sysmlpy 0.84.0__tar.gz → 0.85.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.
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/PKG-INFO +1 -1
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/pyproject.toml +1 -1
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/__init__.py +1 -1
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/evaluator.py +454 -24
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/classes.py +9 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/interchange.py +187 -14
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/LICENSE +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/README.md +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/__main__.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr/README.md +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr/SysMLv2Lexer.interp +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr/SysMLv2Lexer.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr/SysMLv2Lexer.tokens +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr/SysMLv2Parser.interp +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr/SysMLv2Parser.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr/SysMLv2Parser.tokens +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr/SysMLv2ParserListener.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr/SysMLv2ParserVisitor.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.interp +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.tokens +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr_parser.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/antlr_visitor.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/boxes_view.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/definition.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/dfa_cache.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/diff.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/examples/attribute_values.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/examples/part_attributes.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/examples/tuples_sequences.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/formatting.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/README.md +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.g4 +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.interp +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/SysMLv2Lexer.tokens +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.g4 +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.interp +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/SysMLv2Parser.tokens +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/SysMLv2ParserListener.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/SysMLv2ParserVisitor.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/desc.xml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/examples/camera.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/examples/toaster-system.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/examples/vehicle-model.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/grammar/antlr4/pom.xml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/LICENSE +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/README.md +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/__init__.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Analysis/AnalysisTooling.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Analysis/SampledFunctions.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Analysis/StateSpaceRepresentation.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Analysis/TradeStudies.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Cause and Effect/CausationConnections.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Cause and Effect/CauseAndEffect.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Geometry/ShapeItems.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Geometry/SpatialItems.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Metadata/ImageMetadata.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Metadata/ModelingMetadata.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Metadata/ParametersOfInterestMetadata.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Metadata/RiskMetadata.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/ISQ.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/ISQAcoustics.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/ISQAtomicNuclear.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/ISQBase.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/ISQCharacteristicNumbers.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/ISQChemistryMolecular.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/ISQCondensedMatter.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/ISQElectromagnetism.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/ISQInformation.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/ISQLight.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/ISQMechanics.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/ISQSpaceTime.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/ISQThermodynamics.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/MeasurementRefCalculations.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/MeasurementReferences.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/Quantities.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/QuantityCalculations.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/SI.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/SIPrefixes.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/TensorCalculations.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/Time.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/USCustomaryUnits.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Quantities and Units/VectorCalculations.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Requirement Derivation/DerivationConnections.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/domain/Requirement Derivation/RequirementDerivation.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/Base.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/BaseFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/BooleanFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/Clocks.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/CollectionFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/Collections.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/ComplexFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/ControlFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/ControlPerformances.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/DataFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/FeatureReferencingPerformances.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/IntegerFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/KerML.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/Links.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/Metaobjects.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/NaturalFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/NumericalFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/Objects.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/Observation.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/OccurrenceFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/Occurrences.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/Performances.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/RationalFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/RealFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/ScalarFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/ScalarValues.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/SequenceFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/SpatialFrames.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/StatePerformances.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/StringFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/Transfers.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/TransitionPerformances.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/TrigFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/Triggers.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/VectorFunctions.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/kernel/VectorValues.kerml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Actions.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Allocations.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/AnalysisCases.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Attributes.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Calculations.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Cases.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Connections.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Constraints.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Flows.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Interfaces.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Items.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Metadata.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Parts.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Ports.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Requirements.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/StandardViewDefinitions.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/States.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/SysML.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/UseCases.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/VerificationCases.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/library/systems/Views.sysml +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/lsp/__init__.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/lsp/__main__.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/lsp/protocol.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/lsp/server.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/lsp/stdio.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/navigate.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/plantuml.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/project.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/semantic.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/sim.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/spreadsheet.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/store.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/traceability.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/us_customary_units.txt +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/usage.py +0 -0
- {sysmlpy-0.84.0 → sysmlpy-0.85.0}/src/sysmlpy/validator.py +0 -0
|
@@ -34,7 +34,7 @@ __all__ = [
|
|
|
34
34
|
"render_state_transition_view_svg", "boxes_view",
|
|
35
35
|
]
|
|
36
36
|
__author__ = "Jon Fox"
|
|
37
|
-
__version__ = "0.
|
|
37
|
+
__version__ = "0.85.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 (
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
593
|
-
"
|
|
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
|
-
|
|
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
|
|
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
|
-
``
|
|
1119
|
-
|
|
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
|
-
|
|
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):
|