sysmlpy 0.84.0__tar.gz → 0.86.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 (160) hide show
  1. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/PKG-INFO +1 -1
  2. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/pyproject.toml +1 -1
  3. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/__init__.py +1 -1
  4. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/evaluator.py +454 -24
  5. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/classes.py +9 -0
  6. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/interchange.py +187 -14
  7. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/store.py +213 -2
  8. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/LICENSE +0 -0
  9. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/README.md +0 -0
  10. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/__main__.py +0 -0
  11. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr/README.md +0 -0
  12. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr/SysMLv2Lexer.interp +0 -0
  13. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr/SysMLv2Lexer.py +0 -0
  14. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr/SysMLv2Lexer.tokens +0 -0
  15. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr/SysMLv2Parser.interp +0 -0
  16. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr/SysMLv2Parser.py +0 -0
  17. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr/SysMLv2Parser.tokens +0 -0
  18. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr/SysMLv2ParserListener.py +0 -0
  19. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr/SysMLv2ParserVisitor.py +0 -0
  20. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.interp +0 -0
  21. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.py +0 -0
  22. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.tokens +0 -0
  23. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr_parser.py +0 -0
  24. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/antlr_visitor.py +0 -0
  25. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/boxes_view.py +0 -0
  26. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/definition.py +0 -0
  27. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/dfa_cache.py +0 -0
  28. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/diff.py +0 -0
  29. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/examples/attribute_values.py +0 -0
  30. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/examples/part_attributes.py +0 -0
  31. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/examples/tuples_sequences.py +0 -0
  32. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/formatting.py +0 -0
  33. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/README.md +0 -0
  34. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.g4 +0 -0
  35. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.interp +0 -0
  36. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.py +0 -0
  37. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.tokens +0 -0
  38. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.g4 +0 -0
  39. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.interp +0 -0
  40. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.py +0 -0
  41. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.tokens +0 -0
  42. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/SysMLv2ParserListener.py +0 -0
  43. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/SysMLv2ParserVisitor.py +0 -0
  44. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/desc.xml +0 -0
  45. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/examples/camera.sysml +0 -0
  46. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/examples/toaster-system.sysml +0 -0
  47. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/examples/vehicle-model.sysml +0 -0
  48. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/grammar/antlr4/pom.xml +0 -0
  49. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/LICENSE +0 -0
  50. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/README.md +0 -0
  51. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/__init__.py +0 -0
  52. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Analysis/AnalysisTooling.sysml +0 -0
  53. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Analysis/SampledFunctions.sysml +0 -0
  54. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Analysis/StateSpaceRepresentation.sysml +0 -0
  55. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Analysis/TradeStudies.sysml +0 -0
  56. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Cause and Effect/CausationConnections.sysml +0 -0
  57. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Cause and Effect/CauseAndEffect.sysml +0 -0
  58. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Geometry/ShapeItems.sysml +0 -0
  59. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Geometry/SpatialItems.sysml +0 -0
  60. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Metadata/ImageMetadata.sysml +0 -0
  61. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Metadata/ModelingMetadata.sysml +0 -0
  62. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Metadata/ParametersOfInterestMetadata.sysml +0 -0
  63. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Metadata/RiskMetadata.sysml +0 -0
  64. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/ISQ.sysml +0 -0
  65. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/ISQAcoustics.sysml +0 -0
  66. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/ISQAtomicNuclear.sysml +0 -0
  67. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/ISQBase.sysml +0 -0
  68. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/ISQCharacteristicNumbers.sysml +0 -0
  69. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/ISQChemistryMolecular.sysml +0 -0
  70. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/ISQCondensedMatter.sysml +0 -0
  71. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/ISQElectromagnetism.sysml +0 -0
  72. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/ISQInformation.sysml +0 -0
  73. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/ISQLight.sysml +0 -0
  74. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/ISQMechanics.sysml +0 -0
  75. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/ISQSpaceTime.sysml +0 -0
  76. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/ISQThermodynamics.sysml +0 -0
  77. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/MeasurementRefCalculations.sysml +0 -0
  78. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/MeasurementReferences.sysml +0 -0
  79. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/Quantities.sysml +0 -0
  80. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/QuantityCalculations.sysml +0 -0
  81. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/SI.sysml +0 -0
  82. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/SIPrefixes.sysml +0 -0
  83. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/TensorCalculations.sysml +0 -0
  84. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/Time.sysml +0 -0
  85. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/USCustomaryUnits.sysml +0 -0
  86. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Quantities and Units/VectorCalculations.sysml +0 -0
  87. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Requirement Derivation/DerivationConnections.sysml +0 -0
  88. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/domain/Requirement Derivation/RequirementDerivation.sysml +0 -0
  89. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/Base.kerml +0 -0
  90. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/BaseFunctions.kerml +0 -0
  91. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/BooleanFunctions.kerml +0 -0
  92. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/Clocks.kerml +0 -0
  93. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/CollectionFunctions.kerml +0 -0
  94. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/Collections.kerml +0 -0
  95. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/ComplexFunctions.kerml +0 -0
  96. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/ControlFunctions.kerml +0 -0
  97. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/ControlPerformances.kerml +0 -0
  98. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/DataFunctions.kerml +0 -0
  99. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/FeatureReferencingPerformances.kerml +0 -0
  100. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/IntegerFunctions.kerml +0 -0
  101. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/KerML.kerml +0 -0
  102. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/Links.kerml +0 -0
  103. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/Metaobjects.kerml +0 -0
  104. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/NaturalFunctions.kerml +0 -0
  105. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/NumericalFunctions.kerml +0 -0
  106. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/Objects.kerml +0 -0
  107. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/Observation.kerml +0 -0
  108. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/OccurrenceFunctions.kerml +0 -0
  109. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/Occurrences.kerml +0 -0
  110. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/Performances.kerml +0 -0
  111. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/RationalFunctions.kerml +0 -0
  112. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/RealFunctions.kerml +0 -0
  113. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/ScalarFunctions.kerml +0 -0
  114. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/ScalarValues.kerml +0 -0
  115. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/SequenceFunctions.kerml +0 -0
  116. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/SpatialFrames.kerml +0 -0
  117. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/StatePerformances.kerml +0 -0
  118. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/StringFunctions.kerml +0 -0
  119. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/Transfers.kerml +0 -0
  120. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/TransitionPerformances.kerml +0 -0
  121. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/TrigFunctions.kerml +0 -0
  122. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/Triggers.kerml +0 -0
  123. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/VectorFunctions.kerml +0 -0
  124. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/kernel/VectorValues.kerml +0 -0
  125. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Actions.sysml +0 -0
  126. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Allocations.sysml +0 -0
  127. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/AnalysisCases.sysml +0 -0
  128. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Attributes.sysml +0 -0
  129. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Calculations.sysml +0 -0
  130. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Cases.sysml +0 -0
  131. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Connections.sysml +0 -0
  132. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Constraints.sysml +0 -0
  133. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Flows.sysml +0 -0
  134. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Interfaces.sysml +0 -0
  135. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Items.sysml +0 -0
  136. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Metadata.sysml +0 -0
  137. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Parts.sysml +0 -0
  138. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Ports.sysml +0 -0
  139. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Requirements.sysml +0 -0
  140. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/StandardViewDefinitions.sysml +0 -0
  141. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/States.sysml +0 -0
  142. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/SysML.sysml +0 -0
  143. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/UseCases.sysml +0 -0
  144. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/VerificationCases.sysml +0 -0
  145. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/library/systems/Views.sysml +0 -0
  146. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/lsp/__init__.py +0 -0
  147. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/lsp/__main__.py +0 -0
  148. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/lsp/protocol.py +0 -0
  149. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/lsp/server.py +0 -0
  150. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/lsp/stdio.py +0 -0
  151. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/navigate.py +0 -0
  152. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/plantuml.py +0 -0
  153. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/project.py +0 -0
  154. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/semantic.py +0 -0
  155. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/sim.py +0 -0
  156. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/spreadsheet.py +0 -0
  157. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/traceability.py +0 -0
  158. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/us_customary_units.txt +0 -0
  159. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/usage.py +0 -0
  160. {sysmlpy-0.84.0 → sysmlpy-0.86.0}/src/sysmlpy/validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sysmlpy
3
- Version: 0.84.0
3
+ Version: 0.86.0
4
4
  Summary:
5
5
  License-File: LICENSE
6
6
  Author: Jon R. Fox (mycr0ft)
@@ -6,7 +6,7 @@ build-backend = "poetry.core.masonry.api"
6
6
 
7
7
  [project]
8
8
  name = "sysmlpy"
9
- version = "0.84.0"
9
+ version = "0.86.0"
10
10
  description = ""
11
11
  authors = [{ name = "Jon R. Fox (mycr0ft)", email = "jon.fox@drfox.com" }]
12
12
  readme = "README.md"
@@ -34,7 +34,7 @@ __all__ = [
34
34
  "render_state_transition_view_svg", "boxes_view",
35
35
  ]
36
36
  __author__ = "Jon Fox"
37
- __version__ = "0.84.0"
37
+ __version__ = "0.86.0"
38
38
 
39
39
  from sysmlpy.usage import (
40
40
  Item, Attribute, Part, Port, Action, Reference, UseCase, Requirement, Interface, Message,
@@ -27,10 +27,18 @@ Supported expression subset (SysML v2 textual):
27
27
  - references: feature names resolved through the ownership chain;
28
28
  dotted feature chains (``wheels.mass``) with type fallback
29
29
  (``part w : W`` resolves through ``W``'s values)
30
+ - conditionals: ``if <cond> ? <then> else <else>`` (the vendored
31
+ grammar's conditional spelling); condition must be boolean
32
+ - calc invocation: user-defined ``calc def`` bodies collected by the
33
+ collector are invocable as ``name(arg, ...)``; positional ``in``
34
+ parameters bind argument values, declared defaults fill gaps, and
35
+ recursion is supported up to a fixed depth
30
36
 
31
37
  Not supported (raise :class:`UnsupportedExpressionError` with a clear
32
- message): conditional (``? :``) expressions, sequences/collections,
33
- metadata access, casts, string concatenation, calc ``in`` parameters.
38
+ message): the ``cond ? a : b`` conditional spelling (not accepted by
39
+ the vendored grammar at parse time), sequences/collections, metadata
40
+ access, casts, string concatenation, named invocation arguments,
41
+ ``out`` parameter binding.
34
42
 
35
43
  Public surface:
36
44
 
@@ -254,7 +262,12 @@ class _Collector:
254
262
  if name in _CALC_TYPES:
255
263
  expr = self._body_expression(body)
256
264
  if expr is not None:
257
- self.calcs["::".join(qname + (dn,))] = (expr, list(scope))
265
+ # scope: the calc's own namespace first (body locals
266
+ # are visible), then the enclosing namespaces.
267
+ self.calcs["::".join(qname + (dn,))] = (
268
+ expr, [child] + list(scope),
269
+ self._calc_parameters(body),
270
+ )
258
271
  ns.values.setdefault(dn, ("calc", expr))
259
272
  for k, v in node.items():
260
273
  if k not in ("declaration", "body"):
@@ -279,6 +292,63 @@ class _Collector:
279
292
 
280
293
  # -- carriers --------------------------------------------------------
281
294
 
295
+ @staticmethod
296
+ def _calc_parameters(body):
297
+ """Ordered [(name, direction, default expr | None), ...] for the
298
+ ``in``-parameter declarations in a calculation/function body.
299
+
300
+ The visitor emits each parameter as a direction-bearing usage
301
+ (``RefPrefix.direction``) inside the calc body; defaults come
302
+ from the usage's value part.
303
+ """
304
+ params = []
305
+
306
+ def scan(node):
307
+ if isinstance(node, dict):
308
+ name = node.get("name")
309
+ prefix = node.get("prefix")
310
+ direction = None
311
+ if isinstance(name, str) and name.endswith("Usage") \
312
+ and isinstance(prefix, dict):
313
+ # the visitor emits RefPrefix both as the prefix
314
+ # node itself and nested under a "RefPrefix" key
315
+ ref_prefix = prefix.get("RefPrefix")
316
+ if not isinstance(ref_prefix, dict) \
317
+ and prefix.get("name") == "RefPrefix":
318
+ ref_prefix = prefix
319
+ if isinstance(ref_prefix, dict):
320
+ d = ref_prefix.get("direction")
321
+ if isinstance(d, dict):
322
+ for key in ("in", "inout", "out"):
323
+ if d.get(key):
324
+ direction = key
325
+ break
326
+ if direction:
327
+ dn = _declared_name(node.get("declaration"))
328
+ if dn:
329
+ # defaults sit at usage.valuepart on calc-body
330
+ # items, and at completion.valuepart elsewhere
331
+ valuepart = node.get("valuepart")
332
+ if not isinstance(valuepart, dict):
333
+ completion = node.get("completion")
334
+ valuepart = (
335
+ completion.get("valuepart")
336
+ if isinstance(completion, dict) else None
337
+ )
338
+ default = (
339
+ _find_first(valuepart, "OwnedExpression")
340
+ if isinstance(valuepart, dict) else None
341
+ )
342
+ params.append((dn, direction, default))
343
+ for v in node.values():
344
+ scan(v)
345
+ elif isinstance(node, list):
346
+ for item in node:
347
+ scan(item)
348
+
349
+ scan(body)
350
+ return params
351
+
282
352
  def _handle_usage(self, node, ns):
283
353
  completion = node.get("completion")
284
354
  valuepart = (
@@ -392,12 +462,16 @@ _ARITH_OPS = {"+", "-", "*", "/", "%", "**"}
392
462
  class _Evaluator:
393
463
  """Evaluate OwnedExpression dicts against a namespace scope."""
394
464
 
395
- def __init__(self, scope, bindings=None, memo=None, types=None):
465
+ def __init__(self, scope, bindings=None, memo=None, types=None,
466
+ calcs=None):
396
467
  # scope: innermost-first list of _Namespace
397
468
  self.scope = list(scope)
398
469
  self.bindings = dict(bindings or {})
399
470
  self.memo = {} if memo is None else memo
400
471
  self.types = types if types is not None else {}
472
+ # qname string -> (result expr dict, scope namespaces, params)
473
+ self.calcs = calcs if calcs is not None else {}
474
+ self._calc_depth = 0
401
475
 
402
476
  # -- name resolution -------------------------------------------------
403
477
 
@@ -587,12 +661,27 @@ class _Evaluator:
587
661
  )
588
662
 
589
663
  def _eval_conditional(self, node):
590
- if node.get("operator"):
664
+ operator = node.get("operator")
665
+ operand = node.get("operand")
666
+ # Structured shape: operator list carries the if/else markers and
667
+ # operand carries [condition, then-expression, else-expression]
668
+ # (the visitor currently emits ternaries as glued text, so this
669
+ # shape mainly arises from programmatically built dicts).
670
+ if isinstance(operator, list) and len(operator) >= 2 \
671
+ and isinstance(operand, list) and len(operand) >= 3:
672
+ cond = self.evaluate(operand[0])
673
+ if not isinstance(cond, bool):
674
+ raise EvaluationError(
675
+ "conditional condition must be boolean, got "
676
+ f"{type(cond).__name__}: {cond!r}"
677
+ )
678
+ return self.evaluate(operand[1] if cond else operand[2])
679
+ if operator:
591
680
  raise UnsupportedExpressionError(
592
- "conditional expressions ('? :' / 'if then else') are not "
593
- "supported by the evaluator"
681
+ "unsupported conditional operator shape "
682
+ f"({operator!r} with {len(operand or [])} operand(s)) — "
683
+ "the evaluator supports 'if <cond> ? <then> else <else>'"
594
684
  )
595
- operand = node.get("operand")
596
685
  if isinstance(operand, list) and operand:
597
686
  return self.evaluate(operand[0])
598
687
  raise EvaluationError("Empty conditional expression")
@@ -724,6 +813,8 @@ class _Evaluator:
724
813
  "(recursion limit)"
725
814
  )
726
815
  stripped = text.strip()
816
+ if _has_bare_question(stripped):
817
+ return self._eval_ternary_text(stripped)
727
818
  if stripped.endswith("]") and "[" in stripped \
728
819
  and not stripped.startswith('"'):
729
820
  idx = stripped.index("[")
@@ -756,12 +847,6 @@ class _Evaluator:
756
847
  type_node = owned.get("type") or {}
757
848
  qn = (type_node.get("type") or {}).get("names") or []
758
849
  func_name = qn[-1] if qn else None
759
- func = _FUNCTIONS.get(func_name)
760
- if func is None:
761
- raise UnsupportedExpressionError(
762
- f"Unknown function '{func_name}' — supported: "
763
- f"{sorted(_FUNCTIONS)}"
764
- )
765
850
  args = []
766
851
  arg_list = rel.get("arg_list") or {}
767
852
  pos = (arg_list.get("pos_list") or {}).get("ownedRelationship") or []
@@ -773,15 +858,351 @@ class _Evaluator:
773
858
  oe = val.get("ownedRelatedElement")
774
859
  if isinstance(oe, dict):
775
860
  args.append(self.evaluate(oe))
776
- return func(*args)
861
+ func = _FUNCTIONS.get(func_name)
862
+ if func is not None:
863
+ return func(*args)
864
+ return self._invoke_calc(func_name, args)
865
+
866
+ def _eval_fragment(self, text):
867
+ """Evaluate one conditional part: nested ternaries recurse,
868
+ everything else parses and evaluates in scope."""
869
+ frag = text.strip()
870
+ if frag.startswith("if") and _has_bare_question(frag):
871
+ value = self._eval_ternary_text(frag)
872
+ if value is None:
873
+ raise EvaluationError(
874
+ f"Cannot evaluate conditional fragment {frag!r}"
875
+ )
876
+ return value
877
+ return self._eval_text_in_scope(frag)
878
+
879
+ # -- user-defined calc invocation --------------------------------
880
+
881
+ def _lookup_calc(self, name):
882
+ """Find a collected calc def by bare or qualified name."""
883
+ calcs = self.calcs or {}
884
+ entry = calcs.get(name)
885
+ if entry is not None:
886
+ return entry
887
+ matches = [e for q, e in calcs.items()
888
+ if q.rsplit("::", 1)[-1] == name]
889
+ if len(matches) == 1:
890
+ return matches[0]
891
+ if len(matches) > 1:
892
+ raise EvaluationError(
893
+ f"Ambiguous calculation '{name}' — defined as "
894
+ f"{sorted(q for q in calcs if q.rsplit('::', 1)[-1] == name)}"
895
+ )
896
+ return None
897
+
898
+ def _invoke_calc(self, name, args):
899
+ entry = self._lookup_calc(name)
900
+ if entry is None:
901
+ raise UnsupportedExpressionError(
902
+ f"Unknown function '{name}' — supported built-ins: "
903
+ f"{sorted(_FUNCTIONS)}; calc defs must be defined in the "
904
+ "model to be invocable"
905
+ )
906
+ expr, scope, params = entry
907
+ bindings = _bind_calc_args(params, args, scope, self.types,
908
+ self.calcs, self._calc_depth, name)
909
+ child = _Evaluator(scope, bindings=bindings, types=self.types,
910
+ calcs=self.calcs)
911
+ child._calc_depth = self._calc_depth + 1
912
+ if child._calc_depth > _CALC_RECURSION_LIMIT:
913
+ raise EvaluationError(
914
+ f"calc recursion limit ({_CALC_RECURSION_LIMIT}) "
915
+ f"exceeded evaluating '{name}'"
916
+ )
917
+ return child.evaluate(expr)
918
+
919
+ # -- conditional expressions --------------------------------------
920
+
921
+ def _eval_ternary_text(self, text):
922
+ """Evaluate glued text containing a conditional expression.
923
+
924
+ Handles both ``if <cond> ? <then> else <else>`` at the top of
925
+ the text and conditional expressions embedded in parentheses
926
+ inside a larger glued expression (the latter via placeholder
927
+ substitution — the sub-values are bound, not stringified, so
928
+ pint quantities keep their units).
929
+ """
930
+ stripped = text.strip()
931
+ if stripped.startswith("if") and len(stripped) > 4:
932
+ parts = _split_ternary(stripped)
933
+ if parts is None:
934
+ return None
935
+ cond_text, then_text, else_text = parts
936
+ cond = self._eval_fragment(cond_text)
937
+ if not isinstance(cond, bool):
938
+ raise EvaluationError(
939
+ "conditional condition must be boolean, got "
940
+ f"{type(cond).__name__}: {cond!r}"
941
+ )
942
+ return self._eval_fragment(then_text if cond else else_text)
943
+ regions = _paren_ternary_regions(stripped)
944
+ if not regions:
945
+ return None
946
+ bindings = {}
947
+ skeleton = []
948
+ last = 0
949
+ for start, end in regions:
950
+ value = self._eval_ternary_text(stripped[start + 1:end - 1])
951
+ if value is None:
952
+ return None
953
+ name = f"__t{len(bindings)}"
954
+ bindings[name] = value
955
+ skeleton.append(stripped[last:start])
956
+ skeleton.append(name)
957
+ last = end
958
+ skeleton.append(stripped[last:])
959
+ sub = _Evaluator(self.scope,
960
+ bindings={**self.bindings, **bindings},
961
+ types=self.types, calcs=self.calcs)
962
+ sub._calc_depth = self._calc_depth
963
+ try:
964
+ expr_dict = _parse_expression_text("".join(skeleton))
965
+ except EvaluationError:
966
+ return None
967
+ return sub.evaluate(expr_dict)
777
968
 
778
969
 
779
970
  def _looks_like_expression(text):
780
971
  """Heuristic: a "name" containing operators is glued expression text."""
781
- return isinstance(text, str) and any(c in text for c in "+-*/%([") and \
972
+ return isinstance(text, str) and \
973
+ any(c in text for c in "+-*/%([<>?=!") and \
782
974
  not text.strip().startswith('"')
783
975
 
784
976
 
977
+ # ---------------------------------------------------------------------------
978
+ # conditional-expression text splitting (v0.85.0 — Goal 11 Batch 6)
979
+ # ---------------------------------------------------------------------------
980
+
981
+ # The vendored grammar's conditional spelling is
982
+ # ``IF ownedExpression QUESTION ownedExpression ELSE ownedExpression``
983
+ # (no ``then`` keyword, no ``? :`` form). The visitor cannot structure
984
+ # ternaries and falls back to glued text (``getText()`` — all whitespace
985
+ # stripped), so the evaluator splits the glued text here.
986
+
987
+ _CALC_RECURSION_LIMIT = 32
988
+
989
+
990
+ def _has_bare_question(text):
991
+ """True when *text* contains a ``?`` ternary operator anywhere.
992
+
993
+ Skips string literals and the ``?.`` / ``??`` tokens (feature-chain
994
+ and null-coalescing), which are different lexer tokens. A bare
995
+ ``?`` inside parentheses still counts — that is exactly the shape
996
+ of a conditional embedded in a larger glued expression.
997
+ """
998
+ i = 0
999
+ n = len(text)
1000
+ while i < n:
1001
+ c = text[i]
1002
+ if c in ('"', "'"):
1003
+ i += 1
1004
+ while i < n and text[i] != c:
1005
+ if text[i] == "\\":
1006
+ i += 1
1007
+ i += 1
1008
+ elif c == "?":
1009
+ nxt = text[i + 1:i + 2]
1010
+ if nxt in (".", "?"):
1011
+ i += 2
1012
+ continue
1013
+ return True
1014
+ i += 1
1015
+ return False
1016
+
1017
+
1018
+ def _find_bare_question(text, start, end=None):
1019
+ """Index of the first top-level bare ``?`` in text[start:end]."""
1020
+ depth = 0
1021
+ i = start
1022
+ n = len(text) if end is None else end
1023
+ while i < n:
1024
+ c = text[i]
1025
+ if c in "([{":
1026
+ depth += 1
1027
+ elif c in ")]}":
1028
+ depth = max(0, depth - 1)
1029
+ elif c in ('"', "'"):
1030
+ i += 1
1031
+ while i < n and text[i] != c:
1032
+ if text[i] == "\\":
1033
+ i += 1
1034
+ i += 1
1035
+ elif depth == 0 and c == "?":
1036
+ nxt = text[i + 1:i + 2]
1037
+ if nxt in (".", "?"):
1038
+ i += 2
1039
+ continue
1040
+ return i
1041
+ i += 1
1042
+ return -1
1043
+
1044
+
1045
+ def _top_level_occurrences(text, word, start, end=None):
1046
+ """Yield start indices of *word* at bracket depth 0 (strings skipped)."""
1047
+ depth = 0
1048
+ i = start
1049
+ n = len(text) if end is None else end
1050
+ lw = len(word)
1051
+ while i < n:
1052
+ c = text[i]
1053
+ if c in "([{":
1054
+ depth += 1
1055
+ elif c in ")]}":
1056
+ depth = max(0, depth - 1)
1057
+ elif c in ('"', "'"):
1058
+ i += 1
1059
+ while i < n and text[i] != c:
1060
+ if text[i] == "\\":
1061
+ i += 1
1062
+ i += 1
1063
+ elif depth == 0 and text.startswith(word, i):
1064
+ yield i
1065
+ i += lw
1066
+ continue
1067
+ i += 1
1068
+
1069
+
1070
+ def _split_ternary(text):
1071
+ """Split ``if <cond> ? <then> else <else>`` text into its three parts.
1072
+
1073
+ Handles glued text (whitespace stripped by ``getText()``) and spaced
1074
+ text alike. ``else`` candidates are tried left to right and both
1075
+ sides must parse, which protects against identifiers that happen to
1076
+ contain "else". Unparenthesized nested ternaries in the *then*
1077
+ position cannot be disambiguated from glued text and are not
1078
+ supported (parenthesize them).
1079
+
1080
+ Returns ``(cond, then, else)`` strings or ``None``.
1081
+ """
1082
+ s = text.strip()
1083
+ if not s.startswith("if") or len(s) <= 4:
1084
+ return None
1085
+ qi = _find_bare_question(s, 2)
1086
+ if qi <= 2:
1087
+ return None
1088
+ cond = s[2:qi]
1089
+ for ei in _top_level_occurrences(s, "else", qi + 1):
1090
+ then_part = s[qi + 1:ei].strip()
1091
+ else_part = s[ei + 4:].strip()
1092
+ if not then_part or not else_part:
1093
+ continue
1094
+ if _fragment_ok(cond) and _fragment_ok(then_part) \
1095
+ and _fragment_ok(else_part):
1096
+ return cond, then_part, else_part
1097
+ return None
1098
+
1099
+
1100
+ def _respace_ternary_glue(text):
1101
+ """Re-add the spacing ``getText()`` stripped from a ternary.
1102
+
1103
+ The visitor falls back to glued text for conditional expressions
1104
+ (``ifx>0?1.0else2.0``), which does not re-parse — ``ifx`` lexes as
1105
+ an identifier. Splitting and re-spacing (``if x>0 ? 1.0 else
1106
+ 2.0``) restores a parseable spelling that re-glues to the same
1107
+ text, so models carrying ternaries dump-round-trip. Used by
1108
+ ``QualifiedName.dump``.
1109
+
1110
+ Returns the spaced text, or ``None`` when *text* is not a glued
1111
+ ternary.
1112
+ """
1113
+ if not isinstance(text, str) or not text.startswith("if") \
1114
+ or not _has_bare_question(text):
1115
+ return None
1116
+ parts = _split_ternary(text)
1117
+ if parts is None:
1118
+ return None
1119
+ cond, then_part, else_part = parts
1120
+ return f"if {cond} ? {then_part} else {else_part}"
1121
+
1122
+
1123
+ def _fragment_ok(text):
1124
+ """A ternary part validates if it parses outright or is itself a
1125
+ splittable conditional (nested else-chains glue their ``if`` into
1126
+ the preceding identifier, so a plain re-parse cannot see them)."""
1127
+ if _expression_parses(text):
1128
+ return True
1129
+ return text.startswith("if") and _has_bare_question(text) \
1130
+ and _split_ternary(text) is not None
1131
+
1132
+
1133
+ def _paren_ternary_regions(text):
1134
+ """Top-level ``(if ... ? ... else ...)`` regions as (start, end) pairs.
1135
+
1136
+ *end* lies just past the closing paren. Only regions whose content
1137
+ starts with ``if`` and carries a bare ``?`` qualify.
1138
+ """
1139
+ regions = []
1140
+ depth = 0
1141
+ open_idx = -1
1142
+ i = 0
1143
+ n = len(text)
1144
+ while i < n:
1145
+ c = text[i]
1146
+ if c in ('"', "'"):
1147
+ i += 1
1148
+ while i < n and text[i] != c:
1149
+ if text[i] == "\\":
1150
+ i += 1
1151
+ i += 1
1152
+ elif c == "(":
1153
+ if depth == 0:
1154
+ open_idx = i
1155
+ depth += 1
1156
+ elif c == ")":
1157
+ depth = max(0, depth - 1)
1158
+ if depth == 0 and open_idx >= 0:
1159
+ inner = text[open_idx + 1:i].strip()
1160
+ if inner.startswith("if") and _has_bare_question(inner):
1161
+ regions.append((open_idx, i + 1))
1162
+ open_idx = -1
1163
+ i += 1
1164
+ return regions
1165
+
1166
+
1167
+ def _expression_parses(text):
1168
+ """Cheap check that *text* parses as a SysML expression."""
1169
+ try:
1170
+ _parse_expression_text(text)
1171
+ return True
1172
+ except EvaluationError:
1173
+ return False
1174
+
1175
+
1176
+ def _bind_calc_args(params, args, scope, types, calcs, depth, calc_name):
1177
+ """Bind invocation arguments to ``in`` parameters positionally.
1178
+
1179
+ Unbound parameters fall back to their declared default value
1180
+ (``in a : Real := 1.0``), evaluated in the calc scope with the
1181
+ bindings accumulated so far.
1182
+ """
1183
+ bindings = {}
1184
+ args = list(args or [])
1185
+ in_params = [prm for prm in params if prm[1] in ("in", "inout")]
1186
+ if len(args) > len(in_params):
1187
+ raise EvaluationError(
1188
+ f"calc '{calc_name}' expects at most {len(in_params)} "
1189
+ f"argument(s), got {len(args)}"
1190
+ )
1191
+ for (pname, _direction, _default), value in zip(in_params, args):
1192
+ bindings[pname] = value
1193
+ for pname, _direction, default_expr in in_params[len(args):]:
1194
+ if default_expr is None:
1195
+ raise EvaluationError(
1196
+ f"calc '{calc_name}': missing value for parameter "
1197
+ f"'{pname}' (and no declared default)"
1198
+ )
1199
+ tmp = _Evaluator(scope, bindings=dict(bindings), types=types,
1200
+ calcs=calcs)
1201
+ tmp._calc_depth = depth + 1
1202
+ bindings[pname] = tmp.evaluate(default_expr)
1203
+ return bindings
1204
+
1205
+
785
1206
  def _base_reference_names(base):
786
1207
  """Name segments of a BaseExpression feature reference, or None."""
787
1208
  if not isinstance(base, dict) or base.get("name") != "BaseExpression":
@@ -1032,7 +1453,8 @@ def collect_values(model, bindings=None):
1032
1453
  out = {}
1033
1454
  for ns, qname in _iter_namespaces(collector.root):
1034
1455
  evaluator = _Evaluator(_namespace_scope(ns), bindings=bindings,
1035
- types=collector.types)
1456
+ types=collector.types,
1457
+ calcs=collector.calcs)
1036
1458
  for name in ns.values:
1037
1459
  full = "::".join(qname + (name,))
1038
1460
  if name in bindings:
@@ -1105,18 +1527,20 @@ def evaluate_expression(expr, model=None, element=None, bindings=None):
1105
1527
  collector = _collect_model(model)
1106
1528
  scope = _scope_chain_for(collector, element) if element else [collector.root]
1107
1529
  evaluator = _Evaluator(scope, bindings=bindings,
1108
- types=collector.types)
1530
+ types=collector.types,
1531
+ calcs=collector.calcs)
1109
1532
  else:
1110
1533
  evaluator = _Evaluator([], bindings=bindings)
1111
1534
  expr_dict = _parse_expression_text(expr)
1112
1535
  return evaluator.evaluate(expr_dict)
1113
1536
 
1114
1537
 
1115
- def evaluate_calculation(model, calc_name, bindings=None):
1538
+ def evaluate_calculation(model, calc_name, bindings=None, args=None):
1116
1539
  """Evaluate a named ``calc def`` result expression.
1117
1540
 
1118
- ``bindings`` supplies name overrides (e.g. ``in`` parameter values).
1119
- Returns the evaluated value.
1541
+ ``args`` binds the calc's ``in`` parameters positionally (declared
1542
+ defaults fill any gap); ``bindings`` supplies name overrides on top
1543
+ (e.g. what-if values). Returns the evaluated value.
1120
1544
  """
1121
1545
  collector = _collect_model(model)
1122
1546
  entry = collector.calcs.get(calc_name)
@@ -1130,8 +1554,13 @@ def evaluate_calculation(model, calc_name, bindings=None):
1130
1554
  f"No calculation named '{calc_name}' — found: "
1131
1555
  f"{sorted(collector.calcs)}"
1132
1556
  )
1133
- expr, scope = entry
1134
- evaluator = _Evaluator(scope, bindings=bindings, types=collector.types)
1557
+ expr, scope, params = entry
1558
+ call_bindings = _bind_calc_args(params, args, scope, collector.types,
1559
+ collector.calcs, 0, calc_name)
1560
+ if bindings:
1561
+ call_bindings.update(bindings)
1562
+ evaluator = _Evaluator(scope, bindings=call_bindings,
1563
+ types=collector.types, calcs=collector.calcs)
1135
1564
  return evaluator.evaluate(expr)
1136
1565
 
1137
1566
 
@@ -1144,7 +1573,8 @@ def check_constraints(model, bindings=None):
1144
1573
  results = []
1145
1574
  for qname, expr, scope, textual in collector.constraints:
1146
1575
  evaluator = _Evaluator(scope, bindings=bindings,
1147
- types=collector.types)
1576
+ types=collector.types,
1577
+ calcs=collector.calcs)
1148
1578
  result = ConstraintResult(qualified_name=qname)
1149
1579
  if expr is None:
1150
1580
  language, text = textual[0]
@@ -9407,6 +9407,15 @@ class QualifiedName:
9407
9407
  self.names.append(name)
9408
9408
 
9409
9409
  def dump(self):
9410
+ if len(self.names) == 1:
9411
+ # A ternary conditional glues into a single ``names`` entry
9412
+ # (visitor fallback); respaced text is required for the
9413
+ # dump to re-parse (``ifx>0?1.0else2.0`` lexes ``ifx`` as
9414
+ # an identifier).
9415
+ from sysmlpy.evaluator import _respace_ternary_glue
9416
+ spaced = _respace_ternary_glue(self.names[0])
9417
+ if spaced is not None:
9418
+ return spaced
9410
9419
  return "::".join(self.names)
9411
9420
 
9412
9421
  def get_definition(self):