From d9628675b5b17dd5670522cc0a71e79f391314ef Mon Sep 17 00:00:00 2001 From: atheate Date: Fri, 28 Aug 2026 09:36:15 +0200 Subject: [PATCH] Fix #300 --- .../RuleProcessor.CollectionProcessing.cs | 11 ++ .../RuleProcessor.PatternHandlers.cs | 39 ++++--- .../HandleBarHelpers/RuleProcessor.cs | 21 ++++ .../10c-Fuel Economy Analysis.sysml | 4 +- .../12b-Allocation-1.sysml | 2 +- .../13a-Model Containment.sysml | 44 +++++++ ...nd Security Features Element Group-1.sysml | 66 +++++++++++ ...nd Security Features Element Group-2.sysml | 62 ++++++++++ ... and Security Features Element Group.sysml | 34 ++++++ .../TextualNotationValidationTestFixture.cs | 4 + .../DefinitionTextualNotationBuilder.cs | 4 +- .../ElementTextualNotationBuilder.cs | 36 +++--- ...erationDefinitionTextualNotationBuilder.cs | 2 +- .../LibraryPackageTextualNotationBuilder.cs | 2 +- ...etadataDefinitionTextualNotationBuilder.cs | 2 +- .../MetadataFeatureTextualNotationBuilder.cs | 2 +- .../MetadataUsageTextualNotationBuilder.cs | 2 +- .../NamespaceTextualNotationBuilder.cs | 2 +- ...urrenceDefinitionTextualNotationBuilder.cs | 4 +- .../OccurrenceUsageTextualNotationBuilder.cs | 8 +- .../PackageTextualNotationBuilder.cs | 2 +- .../PartUsageTextualNotationBuilder.cs | 4 +- .../ReferenceUsageTextualNotationBuilder.cs | 2 +- .../RequirementUsageTextualNotationBuilder.cs | 2 +- .../SharedTextualNotationBuilder.cs | 2 +- .../TypeTextualNotationBuilder.cs | 2 +- .../UsageTextualNotationBuilder.cs | 4 +- .../ExpressionTextualNotationBuilder.cs | 12 +- .../Writers/NameResolutionCache.cs | 107 ++++++++++++++++-- .../TextualNotationValidationExtensions.cs | 97 +++++++++++++++- .../Writers/TextualNotationWriterContext.cs | 67 +++++++++++ 31 files changed, 577 insertions(+), 75 deletions(-) create mode 100644 SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13a-Model Containment.sysml create mode 100644 SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13b-Safety and Security Features Element Group-1.sysml create mode 100644 SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13b-Safety and Security Features Element Group-2.sysml create mode 100644 SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13b-Safety and Security Features Element Group.sysml diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs index 14659ce46..8c3ae3400 100644 --- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs +++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs @@ -333,6 +333,17 @@ private string ResolveContentTypeGuard(string cursorVariableName, TextualNotatio return null; } + // An allowlisted content rule needs an ABSENCE constraint on the referenced element's own + // contents, which body-shape analysis cannot express — delegate to the hand-coded predicate + // rather than emitting the shape-derived type check below. + if (RequiresHandCodedContentGuard(referencedRule.RuleName)) + { + var handCodedGuardVariableName = $"{referencedRule.RuleName.LowerCaseFirstLetter()}Guard{ruleGenerationContext.NarrowedTypeCheckCounter}"; + ruleGenerationContext.NarrowedTypeCheckCounter++; + + return $"{cursorVariableName}.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship {handCodedGuardVariableName} && {handCodedGuardVariableName}.IsValidFor{referencedRule.RuleName}(writerContext)"; + } + var outerTargetName = referencedRule.EffectiveTarget; var outerTargetClass = RuleQueryUtilities.FindClass(umlClass.Cache, outerTargetName); diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs index 2c78bd45a..597c3f448 100644 --- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs +++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs @@ -812,26 +812,25 @@ private void ProcessUnitypedAlternativesWithOneElement(EncodedTextWriter writer, var defaultElement = mappedNonTerminalElements .LastOrDefault(x => x.UmlClass == ruleGenerationContext.NamedElementToGenerate && !whenGuards.ContainsKey(x.RuleElement)); - mappedNonTerminalElements.Sort((a, b) => - { - var aIsDefault = defaultElement.RuleElement != null && a.RuleElement == defaultElement.RuleElement; - var bIsDefault = defaultElement.RuleElement != null && b.RuleElement == defaultElement.RuleElement; - - if (aIsDefault && !bIsDefault) - { - return 1; - } - - if (bIsDefault && !aIsDefault) - { - return -1; - } - - var depthA = a.UmlClass.QueryAllGeneralClassifiers().Count; - var depthB = b.UmlClass.QueryAllGeneralClassifiers().Count; - - return depthB.CompareTo(depthA); - }); + // Ordered by: non-default arms first (the rule's own target class is the catch-all and + // must sit last), then most-derived first so a subtype arm always precedes an arm + // targeting its supertype. + // + // OrderBy/ThenByDescending is STABLE, which is load-bearing rather than incidental: + // arms of equal inheritance depth are mutually disjoint, so their relative order does + // not affect dispatch — but it does affect the emitted TEXT. The previous + // List.Sort is introsort and therefore unstable, and the comparison carried no + // secondary key, so adding one alternative anywhere in a rule could reshuffle unrelated + // equal-depth arms and produce diff noise that reads like a behavioural change but is + // not. Adding AllocationDefinition to DefinitionElement did exactly that, silently + // reordering MetadataDefinition / ViewDefinition / RenderingDefinition. Falling back to + // declaration order keeps every regeneration minimal and deterministic. + mappedNonTerminalElements = + [ + .. mappedNonTerminalElements + .OrderBy(element => defaultElement.RuleElement != null && element.RuleElement == defaultElement.RuleElement) + .ThenByDescending(element => element.UmlClass.QueryAllGeneralClassifiers().Count) + ]; var variableName = "poco"; diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs index 93b5e222b..76087a158 100644 --- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs +++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs @@ -1688,5 +1688,26 @@ private static bool RequiresHandCodedAlternativeGuard(string alternativeRuleName { return string.Equals(alternativeRuleName, "FunctionOperationExpression", StringComparison.Ordinal); } + + /// + /// Returns true when a collection loop's CONTENT guard cannot be derived from the referenced rule's + /// body shape and must be supplied by a hand-coded IsValidFor{Rule} guard. + /// + /// + /// Currently PrefixMetadataMember. The synthesised guard tests only the shape the rule states + /// — an OwningMembership whose ownedRelatedElement contains a MetadataUsage — but + /// what makes the prefix form applicable is what the usage does NOT own. PrefixMetadataUsage : + /// MetadataUsage = ownedRelationship += OwnedFeatureTyping has no MetadataUsageDeclaration + /// and no MetadataBody, so a usage carrying a body (@Safety { ref :>> isMandatory = + /// false; }) cannot be written with # and must fall through to the body form. Body-shape + /// analysis cannot express an ABSENCE constraint on the referenced element's own contents, so the + /// predicate is hand-coded. + /// + /// The KEBNF rule name supplying the loop's content + /// true if the codegen should emit a hand-coded IsValidFor{Rule} guard + private static bool RequiresHandCodedContentGuard(string contentRuleName) + { + return string.Equals(contentRuleName, "PrefixMetadataMember", StringComparison.Ordinal); + } } } diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/10-Analysis and Trades/10c-Fuel Economy Analysis.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/10-Analysis and Trades/10c-Fuel Economy Analysis.sysml index f08b88c28..ecca1254b 100644 --- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/10-Analysis and Trades/10c-Fuel Economy Analysis.sysml +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/10-Analysis and Trades/10c-Fuel Economy Analysis.sysml @@ -13,10 +13,10 @@ package '10c-Fuel Economy Analysis' { require constraint { actualFuelEconomy >= requiredFuelEconomy } } requirement cityFuelEconomyRequirement: FuelEconomyRequirement { - :>> requiredFuelEconomy = 25[(mi / gallon)]; + :>> requiredFuelEconomy = 25[mi / gallon]; } requirement highwayFuelEconomyRequirement: FuelEconomyRequirement { - :>> requiredFuelEconomy = 30[(mi / gallon)]; + :>> requiredFuelEconomy = 30[mi / gallon]; } } package VehicleDesignModel { diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/12-Dependency Relationships/12b-Allocation-1.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/12-Dependency Relationships/12b-Allocation-1.sysml index b52d72a64..89692384f 100644 --- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/12-Dependency Relationships/12b-Allocation-1.sysml +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/12-Dependency Relationships/12b-Allocation-1.sysml @@ -6,7 +6,7 @@ package '12b-Allocation-1' { package RequirementModel { requirement torqueGeneration { subject generator: TorqueGenerator; - require constraint { generator.generateTorque.torque > 0.0[(N * m)] } + require constraint { generator.generateTorque.torque > 0.0[N * m] } } } package LogicalModel { diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13a-Model Containment.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13a-Model Containment.sysml new file mode 100644 index 000000000..eaf4dd217 --- /dev/null +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13a-Model Containment.sysml @@ -0,0 +1,44 @@ +package '13a-Model Containment' { + private import '2a-Parts Interconnection'::*; + private import '8-Requirements'::*; + requirement BodyAndInteriorRequirements { + public import '1'; + } + requirement PowerTrainRequirements; + package 'Vehicle Model' { + doc + /* + * This package is used to represent a top-level "model". + * There is no specific syntax for identifying a package + * used in this way. + */ + + package 'Vehicle1-Configuration' { + alias 'Sport Sedan' for Usages::vehicle1_c1; + public import 'vehicle1_c1 Specification Context'::'vehicle1-c1 Specification'; + } + package 'Vehicle Reference Model' { + doc + /* + * This package is used to represent a "model library". + * There is no specific syntax for identifying a package + * used in this way. + */ + + public import VehicleA; + public import VehicleSubsystems; + } + package VehicleSubsystems { + public import 'Body&Interior'; + public import PowerTrain; + } + package 'Body&Interior' { + public import BodyAndInteriorRequirements; + } + package PowerTrain { + public import Definitions::Engine; + public import Definitions::Transmission; + public import PowerTrainRequirements; + } + } +} diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13b-Safety and Security Features Element Group-1.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13b-Safety and Security Features Element Group-1.sysml new file mode 100644 index 000000000..dc3fc52ce --- /dev/null +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13b-Safety and Security Features Element Group-1.sysml @@ -0,0 +1,66 @@ +package '13b-Safety and Security Features Element Group-1' { + private import ScalarValues::*; + private import AnnotationDefinitions::*; + private import PartsTree::*; + package AnnotationDefinitions { + metadata def Safety { + attribute isMandatory: Boolean; + } + metadata def Security; + } + package PartsTree { + part vehicle { + part interior { + #Security part alarm; + part seatBelt[2] { + @ Safety { + ref :>> isMandatory = true; + } + } + part frontSeat[2]; + part driverAirBag { + @ Safety { + ref :>> isMandatory = false; + } + } + } + part bodyAssy { + part body; + part bumper { + @ Safety { + ref :>> isMandatory = true; + } + } + #Security part keylessEntry; + } + part wheelAssy { + part wheel[2]; + part antilockBrakes[2] { + @ Safety { + ref :>> isMandatory = false; + } + } + } + } + } + package 'Safety Features' { + /* Parts that contribute to safety. */ + public import vehicle::**; + filter @ Safety; + } + package 'Security Features' { + /* Parts that contribute to security. */ + public import vehicle::**; + filter @ Security; + } + package 'Safety & Security Features' { + /* Parts that contribute to safety OR security. */ + public import vehicle::**; + filter @ Safety or @ Security; + } + package 'Mandatory Safety Features' { + /* Parts that contribute to safety AND are mandatory. */ + public import vehicle::**; + filter @ Safety and (as Safety).isMandatory; + } +} diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13b-Safety and Security Features Element Group-2.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13b-Safety and Security Features Element Group-2.sysml new file mode 100644 index 000000000..d279a6952 --- /dev/null +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13b-Safety and Security Features Element Group-2.sysml @@ -0,0 +1,62 @@ +package '13b-Safety and Security Features Element Group-2' { + private import ScalarValues::*; + private import AnnotationDefinitions::*; + private import PartsTree::*; + package AnnotationDefinitions { + metadata def Safety { + attribute isMandatory: Boolean; + } + metadata def Security; + } + package PartsTree { + part vehicle { + part interior { + #Security part alarm; + part seatBelt[2] { + @ Safety { + ref :>> isMandatory = true; + } + } + part frontSeat[2]; + part driverAirBag { + @ Safety { + ref :>> isMandatory = false; + } + } + } + part bodyAssy { + part body; + part bumper { + @ Safety { + ref :>> isMandatory = true; + } + } + #Security part keylessEntry; + } + part wheelAssy { + part wheel[2]; + part antilockBrakes[2] { + @ Safety { + ref :>> isMandatory = false; + } + } + } + } + } + package 'Safety Features' { + /* Parts that contribute to safety. */ + public import vehicle::**[@ '13b-Safety and Security Features Element Group-2'::AnnotationDefinitions::Safety]; + } + package 'Security Features' { + /* Parts that contribute to security. */ + public import vehicle::**[@ '13b-Safety and Security Features Element Group-2'::AnnotationDefinitions::Security]; + } + package 'Safety & Security Features' { + /* Parts that contribute to safety OR security. */ + public import vehicle::**[@ '13b-Safety and Security Features Element Group-2'::AnnotationDefinitions::Safety or @ '13b-Safety and Security Features Element Group-2'::AnnotationDefinitions::Security]; + } + package 'Mandatory Saftey Features' { + /* Parts that contribute to safety AND are mandatory. */ + public import vehicle::**[@ '13b-Safety and Security Features Element Group-2'::AnnotationDefinitions::Safety and (as '13b-Safety and Security Features Element Group-2'::AnnotationDefinitions::Safety).isMandatory]; + } +} diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13b-Safety and Security Features Element Group.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13b-Safety and Security Features Element Group.sysml new file mode 100644 index 000000000..4093da8ed --- /dev/null +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/13-Model Containment/13b-Safety and Security Features Element Group.sysml @@ -0,0 +1,34 @@ +package '13b-Safety and Security Features Element Group' { + part vehicle1_c1 { + part interior { + part alarm; + part seatBelt[2]; + part frontSeat[2]; + part driverAirBag; + } + part bodyAssy { + part body; + part bumper; + part keylessEntry; + } + } + package 'Safety Features' { + /* Parts that contribute to safety. */ + public import vehicle1_c1::interior::seatBelt; + public import vehicle1_c1::interior::driverAirBag; + public import vehicle1_c1::bodyAssy::bumper; + } + package 'Security Features' { + /* Parts that contribute to security. */ + public import vehicle1_c1::interior::alarm; + public import vehicle1_c1::bodyAssy::keylessEntry; + } + package 'Safety & Security Features' { + /* + * Parts that contribute to safety AND + * parts that contribute to security. + */ + public import 'Safety Features'::*; + public import 'Security Features'::*; + } +} diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Writers/TextualNotationValidationTestFixture.cs b/SysML2.NET.Serializer.TextualNotation.Tests/Writers/TextualNotationValidationTestFixture.cs index b91d8a22a..c46eb9cb5 100644 --- a/SysML2.NET.Serializer.TextualNotation.Tests/Writers/TextualNotationValidationTestFixture.cs +++ b/SysML2.NET.Serializer.TextualNotation.Tests/Writers/TextualNotationValidationTestFixture.cs @@ -113,6 +113,10 @@ public void OneTimeTearDown() [TestCase("12-Dependency Relationships", "12a-Dependency.sysmlx")] [TestCase("12-Dependency Relationships", "12b-Allocation-1.sysmlx")] [TestCase("12-Dependency Relationships", "12b-Allocation.sysmlx")] + [TestCase("13-Model Containment", "13a-Model Containment.sysmlx")] + [TestCase("13-Model Containment", "13b-Safety and Security Features Element Group-1.sysmlx")] + [TestCase("13-Model Containment", "13b-Safety and Security Features Element Group-2.sysmlx")] + [TestCase("13-Model Containment", "13b-Safety and Security Features Element Group.sysmlx")] public async Task VerifyValidationTextualNotationXmi(string folderName, string fileName) { var loggerFactory = LoggerFactory.Create(builder => diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/DefinitionTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/DefinitionTextualNotationBuilder.cs index 94a4de217..78f10e691 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/DefinitionTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/DefinitionTextualNotationBuilder.cs @@ -72,7 +72,7 @@ public static void BuildDefinitionPrefix(SysML2.NET.Core.POCO.Systems.Definition SharedTextualNotationBuilder.BuildBasicDefinitionPrefix(poco, writerContext, stringBuilder); } var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; BuildDefinitionExtensionKeyword(poco, writerContext, stringBuilder); @@ -116,7 +116,7 @@ public static void BuildExtendedDefinition(SysML2.NET.Core.POCO.Systems.Definiti SharedTextualNotationBuilder.BuildBasicDefinitionPrefix(poco, writerContext, stringBuilder); } var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; BuildDefinitionExtensionKeyword(poco, writerContext, stringBuilder); diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ElementTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ElementTextualNotationBuilder.cs index e0a07953a..bc374dd2f 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ElementTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ElementTextualNotationBuilder.cs @@ -104,8 +104,11 @@ public static void BuildDefinitionElement(SysML2.NET.Core.POCO.Root.Elements.IEl case SysML2.NET.Core.POCO.Systems.Cases.ICaseDefinition pocoCaseDefinition: CaseDefinitionTextualNotationBuilder.BuildCaseDefinition(pocoCaseDefinition, writerContext, stringBuilder); break; - case SysML2.NET.Core.POCO.Systems.Metadata.IMetadataDefinition pocoMetadataDefinition: - MetadataDefinitionTextualNotationBuilder.BuildMetadataDefinition(pocoMetadataDefinition, writerContext, stringBuilder); + case SysML2.NET.Core.POCO.Systems.Calculations.ICalculationDefinition pocoCalculationDefinition: + CalculationDefinitionTextualNotationBuilder.BuildCalculationDefinition(pocoCalculationDefinition, writerContext, stringBuilder); + break; + case SysML2.NET.Core.POCO.Systems.Constraints.IConstraintDefinition pocoConstraintDefinition: + ConstraintDefinitionTextualNotationBuilder.BuildConstraintDefinition(pocoConstraintDefinition, writerContext, stringBuilder); break; case SysML2.NET.Core.POCO.Systems.Views.IViewDefinition pocoViewDefinition: ViewDefinitionTextualNotationBuilder.BuildViewDefinition(pocoViewDefinition, writerContext, stringBuilder); @@ -113,11 +116,8 @@ public static void BuildDefinitionElement(SysML2.NET.Core.POCO.Root.Elements.IEl case SysML2.NET.Core.POCO.Systems.Views.IRenderingDefinition pocoRenderingDefinition: RenderingDefinitionTextualNotationBuilder.BuildRenderingDefinition(pocoRenderingDefinition, writerContext, stringBuilder); break; - case SysML2.NET.Core.POCO.Systems.Calculations.ICalculationDefinition pocoCalculationDefinition: - CalculationDefinitionTextualNotationBuilder.BuildCalculationDefinition(pocoCalculationDefinition, writerContext, stringBuilder); - break; - case SysML2.NET.Core.POCO.Systems.Constraints.IConstraintDefinition pocoConstraintDefinition: - ConstraintDefinitionTextualNotationBuilder.BuildConstraintDefinition(pocoConstraintDefinition, writerContext, stringBuilder); + case SysML2.NET.Core.POCO.Systems.Metadata.IMetadataDefinition pocoMetadataDefinition: + MetadataDefinitionTextualNotationBuilder.BuildMetadataDefinition(pocoMetadataDefinition, writerContext, stringBuilder); break; case SysML2.NET.Core.POCO.Systems.Parts.IPartDefinition pocoPartDefinition: PartDefinitionTextualNotationBuilder.BuildPartDefinition(pocoPartDefinition, writerContext, stringBuilder); @@ -152,12 +152,12 @@ public static void BuildDefinitionElement(SysML2.NET.Core.POCO.Root.Elements.IEl case SysML2.NET.Core.POCO.Kernel.Packages.ILibraryPackage pocoLibraryPackage: LibraryPackageTextualNotationBuilder.BuildLibraryPackage(pocoLibraryPackage, writerContext, stringBuilder); break; - case SysML2.NET.Core.POCO.Root.Dependencies.IDependency pocoDependency: - DependencyTextualNotationBuilder.BuildDependency(pocoDependency, writerContext, stringBuilder); - break; case SysML2.NET.Core.POCO.Kernel.Packages.IPackage pocoPackage: PackageTextualNotationBuilder.BuildPackage(pocoPackage, writerContext, stringBuilder); break; + case SysML2.NET.Core.POCO.Root.Dependencies.IDependency pocoDependency: + DependencyTextualNotationBuilder.BuildDependency(pocoDependency, writerContext, stringBuilder); + break; case SysML2.NET.Core.POCO.Root.Annotations.IAnnotatingElement pocoAnnotatingElement: AnnotatingElementTextualNotationBuilder.BuildAnnotatingElement(pocoAnnotatingElement, writerContext, stringBuilder); break; @@ -260,12 +260,6 @@ public static void BuildNonFeatureElement(SysML2.NET.Core.POCO.Root.Elements.IEl case SysML2.NET.Core.POCO.Core.Features.IRedefinition pocoRedefinition: RedefinitionTextualNotationBuilder.BuildRedefinition(pocoRedefinition, writerContext, stringBuilder); break; - case SysML2.NET.Core.POCO.Core.Features.ISubsetting pocoSubsetting: - SubsettingTextualNotationBuilder.BuildSubsetting(pocoSubsetting, writerContext, stringBuilder); - break; - case SysML2.NET.Core.POCO.Core.Features.IFeatureTyping pocoFeatureTyping: - FeatureTypingTextualNotationBuilder.BuildFeatureTyping(pocoFeatureTyping, writerContext, stringBuilder); - break; case SysML2.NET.Core.POCO.Core.Classifiers.IClassifier pocoClassifier: ClassifierTextualNotationBuilder.BuildClassifier(pocoClassifier, writerContext, stringBuilder); break; @@ -275,8 +269,11 @@ public static void BuildNonFeatureElement(SysML2.NET.Core.POCO.Root.Elements.IEl case SysML2.NET.Core.POCO.Core.Classifiers.ISubclassification pocoSubclassification: SubclassificationTextualNotationBuilder.BuildSubclassification(pocoSubclassification, writerContext, stringBuilder); break; - case SysML2.NET.Core.POCO.Core.Features.ITypeFeaturing pocoTypeFeaturing: - TypeFeaturingTextualNotationBuilder.BuildTypeFeaturing(pocoTypeFeaturing, writerContext, stringBuilder); + case SysML2.NET.Core.POCO.Core.Features.IFeatureTyping pocoFeatureTyping: + FeatureTypingTextualNotationBuilder.BuildFeatureTyping(pocoFeatureTyping, writerContext, stringBuilder); + break; + case SysML2.NET.Core.POCO.Core.Features.ISubsetting pocoSubsetting: + SubsettingTextualNotationBuilder.BuildSubsetting(pocoSubsetting, writerContext, stringBuilder); break; case SysML2.NET.Core.POCO.Root.Dependencies.IDependency pocoDependency: DependencyTextualNotationBuilder.BuildDependency(pocoDependency, writerContext, stringBuilder); @@ -299,6 +296,9 @@ public static void BuildNonFeatureElement(SysML2.NET.Core.POCO.Root.Elements.IEl case SysML2.NET.Core.POCO.Core.Features.IFeatureInverting pocoFeatureInverting: FeatureInvertingTextualNotationBuilder.BuildFeatureInverting(pocoFeatureInverting, writerContext, stringBuilder); break; + case SysML2.NET.Core.POCO.Core.Features.ITypeFeaturing pocoTypeFeaturing: + TypeFeaturingTextualNotationBuilder.BuildTypeFeaturing(pocoTypeFeaturing, writerContext, stringBuilder); + break; case SysML2.NET.Core.POCO.Root.Namespaces.INamespace pocoNamespace: NamespaceTextualNotationBuilder.BuildNamespace(pocoNamespace, writerContext, stringBuilder); break; diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/EnumerationDefinitionTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/EnumerationDefinitionTextualNotationBuilder.cs index ba786fbfc..8074ed86e 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/EnumerationDefinitionTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/EnumerationDefinitionTextualNotationBuilder.cs @@ -88,7 +88,7 @@ public static void BuildEnumerationBody(SysML2.NET.Core.POCO.Systems.Enumeration public static void BuildEnumerationDefinition(SysML2.NET.Core.POCO.Systems.Enumerations.IEnumerationDefinition poco, TextualNotationWriterContext writerContext, IndentedStringBuilder stringBuilder) { var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; DefinitionTextualNotationBuilder.BuildDefinitionExtensionKeyword(poco, writerContext, stringBuilder); diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/LibraryPackageTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/LibraryPackageTextualNotationBuilder.cs index a26e8eb7b..dd9cba20b 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/LibraryPackageTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/LibraryPackageTextualNotationBuilder.cs @@ -51,7 +51,7 @@ public static void BuildLibraryPackage(SysML2.NET.Core.POCO.Kernel.Packages.ILib stringBuilder.Append(' '); stringBuilder.Append("library "); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { if (ownedRelationshipCursor.Current != null) diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MetadataDefinitionTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MetadataDefinitionTextualNotationBuilder.cs index f731939f3..171df9159 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MetadataDefinitionTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MetadataDefinitionTextualNotationBuilder.cs @@ -50,7 +50,7 @@ public static void BuildMetadataDefinition(SysML2.NET.Core.POCO.Systems.Metadata } var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; DefinitionTextualNotationBuilder.BuildDefinitionExtensionKeyword(poco, writerContext, stringBuilder); diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MetadataFeatureTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MetadataFeatureTextualNotationBuilder.cs index 6f04dc7cb..7e31719e5 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MetadataFeatureTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MetadataFeatureTextualNotationBuilder.cs @@ -100,7 +100,7 @@ public static void BuildMetadataFeature(SysML2.NET.Core.POCO.Kernel.Metadata.IMe { var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { if (ownedRelationshipCursor.Current != null) diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MetadataUsageTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MetadataUsageTextualNotationBuilder.cs index 3d10ae42c..8552cb0f1 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MetadataUsageTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/MetadataUsageTextualNotationBuilder.cs @@ -99,7 +99,7 @@ public static void BuildMetadataUsageDeclaration(SysML2.NET.Core.POCO.Systems.Me public static void BuildMetadataUsage(SysML2.NET.Core.POCO.Systems.Metadata.IMetadataUsage poco, TextualNotationWriterContext writerContext, IndentedStringBuilder stringBuilder) { var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; UsageTextualNotationBuilder.BuildUsageExtensionKeyword(poco, writerContext, stringBuilder); diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/NamespaceTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/NamespaceTextualNotationBuilder.cs index 8eab6fbcb..39b96e8a3 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/NamespaceTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/NamespaceTextualNotationBuilder.cs @@ -162,7 +162,7 @@ public static void BuildNamespace(SysML2.NET.Core.POCO.Root.Namespaces.INamespac { var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { if (ownedRelationshipCursor.Current != null) diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/OccurrenceDefinitionTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/OccurrenceDefinitionTextualNotationBuilder.cs index 191076e0e..587a6e690 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/OccurrenceDefinitionTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/OccurrenceDefinitionTextualNotationBuilder.cs @@ -66,7 +66,7 @@ public static void BuildOccurrenceDefinitionPrefix(SysML2.NET.Core.POCO.Systems. stringBuilder.Append(' '); } - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; DefinitionTextualNotationBuilder.BuildDefinitionExtensionKeyword(poco, writerContext, stringBuilder); @@ -106,7 +106,7 @@ public static void BuildIndividualDefinition(SysML2.NET.Core.POCO.Systems.Occurr { stringBuilder.Append(" individual "); } - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; DefinitionTextualNotationBuilder.BuildDefinitionExtensionKeyword(poco, writerContext, stringBuilder); diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/OccurrenceUsageTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/OccurrenceUsageTextualNotationBuilder.cs index a616cf4da..ecd151449 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/OccurrenceUsageTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/OccurrenceUsageTextualNotationBuilder.cs @@ -60,7 +60,7 @@ public static void BuildOccurrenceUsagePrefix(SysML2.NET.Core.POCO.Systems.Occur } var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; UsageTextualNotationBuilder.BuildUsageExtensionKeyword(poco, writerContext, stringBuilder); @@ -85,7 +85,7 @@ public static void BuildIndividualUsage(SysML2.NET.Core.POCO.Systems.Occurrences stringBuilder.Append(" individual "); } var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; UsageTextualNotationBuilder.BuildUsageExtensionKeyword(poco, writerContext, stringBuilder); @@ -116,7 +116,7 @@ public static void BuildPortionUsage(SysML2.NET.Core.POCO.Systems.Occurrences.IO stringBuilder.Append(poco.PortionKind.ToString().ToLower()); stringBuilder.Append(' '); var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; UsageTextualNotationBuilder.BuildUsageExtensionKeyword(poco, writerContext, stringBuilder); @@ -155,7 +155,7 @@ public static void BuildControlNodePrefix(SysML2.NET.Core.POCO.Systems.Occurrenc } var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; UsageTextualNotationBuilder.BuildUsageExtensionKeyword(poco, writerContext, stringBuilder); diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/PackageTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/PackageTextualNotationBuilder.cs index 288e3e339..b5d2a4564 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/PackageTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/PackageTextualNotationBuilder.cs @@ -163,7 +163,7 @@ public static void BuildPackage(SysML2.NET.Core.POCO.Kernel.Packages.IPackage po { var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { if (ownedRelationshipCursor.Current != null) diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/PartUsageTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/PartUsageTextualNotationBuilder.cs index a573c5b82..0cc17de9e 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/PartUsageTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/PartUsageTextualNotationBuilder.cs @@ -44,7 +44,7 @@ public static void BuildActorUsage(SysML2.NET.Core.POCO.Systems.Parts.IPartUsage { stringBuilder.Append("actor "); var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; UsageTextualNotationBuilder.BuildUsageExtensionKeyword(poco, writerContext, stringBuilder); @@ -66,7 +66,7 @@ public static void BuildStakeholderUsage(SysML2.NET.Core.POCO.Systems.Parts.IPar { stringBuilder.Append("stakeholder "); var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; UsageTextualNotationBuilder.BuildUsageExtensionKeyword(poco, writerContext, stringBuilder); diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ReferenceUsageTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ReferenceUsageTextualNotationBuilder.cs index 2c82f36cc..912dba56c 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ReferenceUsageTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/ReferenceUsageTextualNotationBuilder.cs @@ -309,7 +309,7 @@ public static void BuildSubjectUsage(SysML2.NET.Core.POCO.Systems.DefinitionAndU { stringBuilder.Append("subject "); var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; UsageTextualNotationBuilder.BuildUsageExtensionKeyword(poco, writerContext, stringBuilder); diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/RequirementUsageTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/RequirementUsageTextualNotationBuilder.cs index 11bb91c5e..8cd820e19 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/RequirementUsageTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/RequirementUsageTextualNotationBuilder.cs @@ -43,7 +43,7 @@ public static partial class RequirementUsageTextualNotationBuilder public static void BuildObjectiveRequirementUsage(SysML2.NET.Core.POCO.Systems.Requirements.IRequirementUsage poco, TextualNotationWriterContext writerContext, IndentedStringBuilder stringBuilder) { var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; UsageTextualNotationBuilder.BuildUsageExtensionKeyword(poco, writerContext, stringBuilder); diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SharedTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SharedTextualNotationBuilder.cs index fa1e10279..0811c8c43 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SharedTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/SharedTextualNotationBuilder.cs @@ -130,7 +130,7 @@ public static void BuildFeaturePrefix(SysML2.NET.Core.POCO.Core.Features.IFeatur BuildFeaturePrefixHandCoded(poco, writerContext, stringBuilder); stringBuilder.Append(' '); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { if (ownedRelationshipCursor.Current != null) diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/TypeTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/TypeTextualNotationBuilder.cs index 817dfcc11..5e61cf14b 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/TypeTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/TypeTextualNotationBuilder.cs @@ -500,7 +500,7 @@ public static void BuildTypePrefix(SysML2.NET.Core.POCO.Core.Types.IType poco, T } - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { if (ownedRelationshipCursor.Current != null) diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/UsageTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/UsageTextualNotationBuilder.cs index a43670cd3..f7be10bd5 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/UsageTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/UsageTextualNotationBuilder.cs @@ -207,7 +207,7 @@ public static void BuildUsagePrefix(SysML2.NET.Core.POCO.Systems.DefinitionAndUs { BuildUnextendedUsagePrefix(poco, writerContext, stringBuilder); var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; BuildUsageExtensionKeyword(poco, writerContext, stringBuilder); @@ -627,7 +627,7 @@ public static void BuildExtendedUsage(SysML2.NET.Core.POCO.Systems.DefinitionAnd { BuildUnextendedUsagePrefix(poco, writerContext, stringBuilder); var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship); - while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType().Any()) + while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext)) { var positionBeforeItem0 = ownedRelationshipCursor.Position; BuildUsageExtensionKeyword(poco, writerContext, stringBuilder); diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/ExpressionTextualNotationBuilder.cs b/SysML2.NET.Serializer.TextualNotation/Writers/ExpressionTextualNotationBuilder.cs index efae2498a..0ee52c06c 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/ExpressionTextualNotationBuilder.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/ExpressionTextualNotationBuilder.cs @@ -45,7 +45,17 @@ private static void BuildSequenceExpressionListHandCoded(IExpression poco, Textu } else { - BuildOwnedExpression(poco, writerContext, stringBuilder); + // Every rule that reaches SequenceExpressionList has already emitted the delimiters + // enclosing this single content expression — SequenceExpression ('(' … ')'), + // BracketExpression ('[' … ']') and IndexExpression ('#' '(' … ')') — so + // BuildOwnedExpression's operand-parenthesisation layer would double them, yielding + // ((as Safety)) or 25[(mi / gallon)]. Suspending the operator context for this call + // suppresses exactly that layer; operands nested DEEPER re-push their own context and + // still parenthesise normally. + using (writerContext.SuspendOperatorContext()) + { + BuildOwnedExpression(poco, writerContext, stringBuilder); + } } } } diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/NameResolutionCache.cs b/SysML2.NET.Serializer.TextualNotation/Writers/NameResolutionCache.cs index 63570d1dd..0340093cd 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/NameResolutionCache.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/NameResolutionCache.cs @@ -150,19 +150,45 @@ public string Resolve(IElement target, IElement sourcePoco) case null: return string.Empty; - // Membership imports keep the full path, using the SHORTEST declared name per - // segment (`import SI::kg`, not `SI::kilogram` as qualifiedName would give). case IMembership membership: - return membership.MemberElement != null - ? QueryShortQualifiedName(membership.MemberElement, sourcePoco) - : string.Empty; + if (membership.MemberElement == null) + { + return string.Empty; + } + + // A MembershipImport names its target THROUGH a Membership, so resolving the + // Membership itself would never reach the simple-name index and every membership + // import was emitted ownership-relative — `import PartsTree::vehicle::**` where the + // enclosing namespace already imports `PartsTree::*` and `vehicle` resolves bare. + // Continue with the member element so the ordinary path applies; the import + // self-containment guard immediately below still runs, and the index still declines + // to shorten a name that is shadowed or ambiguous at the reference site. + // Narrowed to IMembershipImport deliberately, so this guard and the self-binding + // exclusion in QuerySelfBinding — which also keys on IMembershipImport — cannot drift + // apart. A rewrite here without the matching exclusion there would reintroduce exactly + // the self-referential match the exclusion exists to prevent. + if (sourcePoco is IMembershipImport) + { + target = membership.MemberElement; + break; + } + + // Elsewhere a Membership target keeps the full path, using the SHORTEST declared name + // per segment (`import SI::kg`, not `SI::kilogram` as qualifiedName would give). + return QueryShortQualifiedName(membership.MemberElement, sourcePoco); } - // A namespace import keeps a SELF-CONTAINED path unless the target is reachable by + // A NAMESPACE import keeps a SELF-CONTAINED path unless the target is reachable by // containment: shortest-name resolution would emit a name that only resolves while a // SIBLING import of the same namespace remains (`import 'provide power'::*` rather than // `import '3a-Function-based Behavior-1'::'provide power'::*`). - if (sourcePoco is IImport { OwningRelatedElement: { } importOwner } + // A MEMBERSHIP import is deliberately exempt. It names ONE element, so the ordinary + // path can shorten it to whatever actually resolves at the reference site and declines + // wherever the name is shadowed or ambiguous — which is what keeps the colliding + // `Engine` / `Transmission` / `vehicle1_c1` qualified in `13a-Model Containment` while + // letting the unambiguous ones match the pilot. The self-referential match that shortening + // would otherwise expose is handled by QuerySelfBinding, not by this guard. + if (sourcePoco is IImport { OwningRelatedElement: { } importOwner } and not IMembershipImport && !IsReachableByContainment(target, importOwner)) { return this.QueryImportPath(target); @@ -226,6 +252,21 @@ IReferenceSubsetting referenceSubsetting when ReferenceEquals(target, referenceS return this.ResolveFresh(target, this.BuildReferenceSite(sourcePoco, sourceLocalScope, matchFloorScope, localReferencer), escapedName); } + var referenceSite = this.BuildReferenceSite(sourcePoco, sourceLocalScope, matchFloorScope, localRedefiner: null); + + // The memo key is (target, localScope, matchFloor) — it does NOT capture the SELF BINDING, which + // is derived from sourcePoco. Two sites sharing that triple but differing in self-binding status + // resolve differently, so caching either one would answer for the other. That shape is ordinary: + // `import Foo::Bar;` and a `part p : Bar;` in the SAME package share the triple, yet the import + // must discount its own binding and the usage must not. Whichever ran first would win — emitting + // either a needlessly long name for the usage, or `import Bar;` that resolves only because of + // itself. Self-binding sites are just import declarations and bare re-export memberships, so + // resolving them fresh costs nothing measurable. + if (referenceSite.SelfBinding != null) + { + return this.ResolveFresh(target, referenceSite, escapedName); + } + var cacheKey = (target.Id, sourceLocalScope?.Id ?? Guid.Empty, matchFloorScope?.Id ?? Guid.Empty); if (this.resolvedReferences.TryGetValue(cacheKey, out var cached)) @@ -233,7 +274,7 @@ IReferenceSubsetting referenceSubsetting when ReferenceEquals(target, referenceS return cached; } - var resolved = this.ResolveFresh(target, this.BuildReferenceSite(sourcePoco, sourceLocalScope, matchFloorScope, localRedefiner: null), escapedName); + var resolved = this.ResolveFresh(target, referenceSite, escapedName); this.resolvedReferences[cacheKey] = resolved; return resolved; } @@ -264,7 +305,7 @@ private static bool IsReachableByContainment(IElement target, IElement importOwn // (`'10a-Analysis'::VehicleDesignModel::Vehicle` from inside `'10a-Analysis'`), which is redundant // self-prefixing. Elements from a separate resource (a library) share no ancestor and still // correctly return false. - for (var scope = importOwner; scope != null; scope = QueryOwningContainer(scope)) + for (var scope = importOwner; scope != null; scope = QueryEnclosingContainer(scope)) { for (var candidate = declaringNamespace; candidate != null; candidate = QueryOwningContainer(candidate)) { @@ -278,6 +319,44 @@ private static bool IsReachableByContainment(IElement target, IElement importOwn return false; } + /// + /// Returns the namespace enclosing , continuing through the CONTAINMENT + /// tree when owningNamespace is null. + /// + /// The element to climb from; may be . + /// The enclosing namespace, or at a true root. + /// + /// A FilterPackage is an ownedRelatedElement of its Import + /// (NamespaceImport = … | importedNamespace = FilterPackage { ownedRelatedElement += + /// importedNamespace }), and an Import is not a Membership — so the FilterPackage + /// has no owningMembership and therefore no owningNamespace, even though it is plainly + /// nested in the model. Climbing by owningNamespace alone dead-ends there and makes every + /// name inside a filtered import look unreachable, forcing the fully root-qualified form. + /// KerML §7.2.5.3 (normative) defines a root namespace as one that has no OWNER, which the + /// FilterPackage has; the owningNamespace-based phrasing in §8.2.3.4.1 is informative. The + /// pilot climbs containment too (NamespaceUtil.getParentNamespaceOf walks + /// eContainer()). A true root still terminates the climb, because it has neither. + /// + private static INamespace QueryEnclosingContainer(IElement element) + { + if (QueryOwningContainer(element) is { } owningNamespace) + { + return owningNamespace; + } + + var visited = new HashSet(); + + for (var container = QueryOwnerSafe(element); container != null && visited.Add(container); container = QueryOwnerSafe(container)) + { + if (container is INamespace enclosingNamespace) + { + return enclosingNamespace; + } + } + + return null; + } + /// /// Returns a SELF-CONTAINED path to , anchored at the outermost named /// ancestor that binds it directly, so the path never depends on names introduced by imports of the @@ -365,6 +444,16 @@ private bool BindsDirectly(INamespace scope, IElement target, string segment) /// The self binding, or when the source is not one. private static SelfBinding QuerySelfBinding(IElement sourcePoco) { + // A MembershipImport binds its imported Membership into the importing Namespace, and that + // binding likewise does not exist yet while the import declaration itself is being written. + // Without discounting it, `public import vehicle1_c1::interior::seatBelt;` resolves its own + // target trivially at depth 0 and emits `public import seatBelt;` — a name that resolves + // ONLY because of the very import it is the declaration of, so it does not re-parse. + if (sourcePoco is IMembershipImport { ImportedMembership: { } importedMembership, OwningRelatedElement: INamespace importScope }) + { + return new SelfBinding(importScope, importedMembership); + } + return sourcePoco is IMembership membership and not IOwningMembership && string.IsNullOrWhiteSpace(membership.MemberName) && string.IsNullOrWhiteSpace(membership.MemberShortName) diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationValidationExtensions.cs b/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationValidationExtensions.cs index 11e7342a2..7fd2d4fc5 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationValidationExtensions.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationValidationExtensions.cs @@ -46,6 +46,7 @@ namespace SysML2.NET.Serializer.TextualNotation.Writers using SysML2.NET.Core.POCO.Systems.Flows; using SysML2.NET.Core.POCO.Systems.Interfaces; using SysML2.NET.Core.POCO.Systems.Items; + using SysML2.NET.Core.POCO.Systems.Metadata; using SysML2.NET.Core.POCO.Systems.Occurrences; using SysML2.NET.Core.POCO.Systems.Parts; using SysML2.NET.Core.POCO.Systems.Ports; @@ -744,6 +745,13 @@ internal static bool IsValidForMetaclassificationExpression(this IOperatorExpres /// ); false otherwise. An /// whose operator is , is admitted despite being an — /// that IS the sequence form this rule exists to render. + /// So is any OTHER that only OwnedExpression can + /// render — see . OperatorExpression IS-A + /// InvocationExpression in the metamodel, so excluding every invocation sent a + /// parenthesised (as Safety) to BaseExpression's InvocationExpression arm, + /// which rendered it as () — an empty instantiated-type name plus an empty argument list. + /// That re-parses as NullExpression ('null' | '(' ')'), a different metaclass, so a + /// filter written over it selects nothing. /// internal static bool IsValidForSequenceExpression(this IExpression expression, TextualNotationWriterContext writerContext) { @@ -753,7 +761,94 @@ internal static bool IsValidForSequenceExpression(this IExpression expression, T && expression is not IFeatureReferenceExpression && expression is not IMetadataAccessExpression && expression is not IConstructorExpression - && (expression is not IInvocationExpression || expression is IOperatorExpression { Operator: "," }); + && (expression is not IInvocationExpression + || expression is IOperatorExpression { Operator: "," } + || (expression is IOperatorExpression operatorExpression && operatorExpression.IsOwnedExpressionOnlyForm(writerContext))); + } + + /// + /// Asserts that the is a form only OwnedExpression renders, + /// and which therefore needs SequenceExpression's parentheses to appear in a primary position. + /// NonFeatureChainPrimaryExpression : Expression = BracketExpression | IndexExpression | + /// SequenceExpression | SelectExpression | CollectExpression | FunctionOperationExpression | + /// BaseExpression — none of those alternatives reaches ConditionalExpression, + /// BinaryOperatorExpression, UnaryOperatorExpression, ClassificationExpression, + /// MetaclassificationExpression or ExtentExpression. The only route is + /// SequenceExpression = '(' SequenceExpressionList ')' → + /// SequenceExpressionList = OwnedExpression ','? | SequenceOperatorExpression. + /// The test is deliberately the DISJUNCTION of the arms BuildOwnedExpression dispatches + /// on, not a blanket "is an OperatorExpression". BuildSequenceExpressionList calls + /// BuildOwnedExpression on the SAME poco, so admitting a form whose OwnedExpression + /// guards all fail would emit (, recurse to NonFeatureChainPrimaryExpression, match here + /// again, and never terminate. Every arm below is one BuildOwnedExpression can actually take, + /// so the recursion always makes progress. + /// The more specific primary forms — BracketExpression (operator [), + /// IndexExpression, SelectExpression, CollectExpression and + /// FunctionOperationExpression — are matched by earlier arms of + /// BuildNonFeatureChainPrimaryExpression, so widening this last-before-default guard cannot + /// steal them. + /// + /// The + /// The active + /// True when only OwnedExpression can render the expression + private static bool IsOwnedExpressionOnlyForm(this IOperatorExpression operatorExpression, TextualNotationWriterContext writerContext) + { + return operatorExpression.IsValidForConditionalExpression(writerContext) + || operatorExpression.IsValidForConditionalBinaryOperatorExpression(writerContext) + || operatorExpression.IsValidForBinaryOperatorExpression(writerContext) + || operatorExpression.IsValidForUnaryOperatorExpression(writerContext) + || operatorExpression.IsValidForClassificationExpression(writerContext) + || operatorExpression.IsValidForMetaclassificationExpression(writerContext) + || operatorExpression.IsValidForExtentExpression(writerContext); + } + + /// + /// Asserts that the is valid for the ExtentExpression rule. + /// ExtentExpression : OperatorExpression = operator = 'all' ownedRelationship += TypeReferenceMember + /// Mirrors the inline condition the generated BuildOwnedExpression arm applies, so + /// tests exactly what that dispatch would take. + /// + /// The + /// The active + /// True when the operator is all and the cursor is at a TypeReferenceMember + private static bool IsValidForExtentExpression(this IOperatorExpression operatorExpression, TextualNotationWriterContext writerContext) + { + return operatorExpression.Operator == "all" + && writerContext.CursorCache + .GetOrCreateCursor(operatorExpression.Id, "ownedRelationship", operatorExpression.OwnedRelationship) + .Current is IParameterMembership; + } + + /// + /// Asserts that the is valid for the PrefixMetadataMember rule. + /// PrefixMetadataMember : OwningMembership = '#' ownedRelatedElement = PrefixMetadataUsage + /// PrefixMetadataUsage : MetadataUsage = ownedRelationship += OwnedFeatureTyping + /// The prefix form carries NOTHING but the typing: PrefixMetadataUsage has no + /// MetadataUsageDeclaration and no MetadataBody, so it cannot express a name or a + /// body. A that owns anything besides its + /// — typically a FeatureMembership holding a + /// MetadataBodyUsage such as ref :>> isMandatory = false — must therefore be + /// declined here so it falls through to the body form @Safety { … }, which can carry it. + /// Without this the dispatch matched on the MetadataUsage's mere presence and emitted + /// #Safety part driverAirBag;, silently discarding the isMandatory value. SysML + /// §7.27.2 is normative that a metadata definition with features requires a body. + /// + /// The at the cursor + /// The active (unused for this guard) + /// True when the relationship is a prefix-expressible metadata membership + internal static bool IsValidForPrefixMetadataMember(this IRelationship relationship, TextualNotationWriterContext writerContext) + { + if (relationship is not IOwningMembership owningMembership) + { + return false; + } + + var metadataUsage = owningMembership.OwnedRelatedElement.OfType().FirstOrDefault(); + + return metadataUsage != null + && string.IsNullOrWhiteSpace(metadataUsage.DeclaredName) + && string.IsNullOrWhiteSpace(metadataUsage.DeclaredShortName) + && metadataUsage.OwnedRelationship.All(owned => owned is IFeatureTyping); } /// diff --git a/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationWriterContext.cs b/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationWriterContext.cs index 059e8da26..3d6a54459 100644 --- a/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationWriterContext.cs +++ b/SysML2.NET.Serializer.TextualNotation/Writers/TextualNotationWriterContext.cs @@ -134,6 +134,33 @@ public TextualNotationWriterContext(INamespace contextNamespace, IEnumerable public Stack OperatorContextStack { get; } + /// + /// Suspends the operator context for the lifetime of the returned scope, so the next operand + /// emission behaves as though its expression were not nested in an operator and adds no + /// parentheses of its own. + /// + /// A scope that restores the operator context when disposed. + /// + /// For a rule that has ALREADY emitted delimiters around a single content expression — + /// SequenceExpression ('(' … ')'), BracketExpression ('[' … ']') and + /// IndexExpression ('#' '(' … ')') — the operand-parenthesisation layer would + /// double the delimiters it already wrote, giving ((as Safety)) or + /// 25[(mi / gallon)]. + /// Suspension is scoped rather than global because it must apply to the IMMEDIATE operand + /// only. Every operator builder pushes its own poco onto the stack on entry, so an operand + /// nested deeper inside the suspended expression sees a non-empty context again and + /// parenthesises normally. + /// It deliberately does NOT extend to ArgumentList ('(' ( PositionalArgumentList | + /// NamedArgumentList )? ')'), whose parentheses delimit a comma-separated LIST rather than a + /// single operand — an argument that is itself a sequence needs its own parentheses there, which + /// is why the pilot writes sum((a, b, c)). ArgumentList does not route through + /// SequenceExpressionList, so it never opens this scope. + /// + public IDisposable SuspendOperatorContext() + { + return new SuspendedOperatorContext(this.OperatorContextStack); + } + /// /// Gets the used for cursor-based element traversal. /// @@ -161,5 +188,45 @@ public void Dispose() this.CursorCache.Dispose(); this.inheritanceScope.Dispose(); } + + /// + /// The scope opened by : drains the operator context on + /// construction and restores it, in its original order, on disposal. + /// + private sealed class SuspendedOperatorContext : IDisposable + { + /// + /// The suspended stack. + /// + private readonly Stack operatorContextStack; + + /// + /// The drained entries, top of stack first. + /// + private readonly IExpression[] suspendedEntries; + + /// + /// Initializes a new instance of the class. + /// + /// The stack to suspend. + internal SuspendedOperatorContext(Stack operatorContextStack) + { + this.operatorContextStack = operatorContextStack; + this.suspendedEntries = operatorContextStack.ToArray(); + operatorContextStack.Clear(); + } + + /// + /// Restores the suspended operator context. + /// + public void Dispose() + { + // ToArray yields top-first, so pushing in reverse restores the original ordering. + for (var entryIndex = this.suspendedEntries.Length - 1; entryIndex >= 0; entryIndex--) + { + this.operatorContextStack.Push(this.suspendedEntries[entryIndex]); + } + } + } } }