diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs
index 8a7b68b0..f90aaf35 100644
--- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs
+++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs
@@ -145,16 +145,7 @@ private void EmitCollectionNonTerminalLoop(EncodedTextWriter writer, IClass umlC
{
writer.WriteSafeString($"while ({whileCondition}){Environment.NewLine}");
writer.WriteSafeString($"{{{Environment.NewLine}");
-
- var previousCaller = ruleGenerationContext.CallerRule;
- var previousName = ruleGenerationContext.CurrentVariableName;
- ruleGenerationContext.CallerRule = nonTerminalElement;
-
- this.ProcessAlternatives(writer, umlClass, referencedRule.Alternatives, ruleGenerationContext);
-
- ruleGenerationContext.CallerRule = previousCaller;
- ruleGenerationContext.CurrentVariableName = previousName;
-
+ this.ProcessReferencedRuleAlternatives(writer, umlClass, nonTerminalElement, referencedRule, ruleGenerationContext);
writer.WriteSafeString($"{Environment.NewLine}}}{Environment.NewLine}");
}
@@ -370,15 +361,10 @@ private static string ResolveBuilderCall(IClass umlClass, NonTerminalElement non
}
///
- /// Generates an inline condition expression for an optional non-terminal reference.
- /// For enumerable properties whose consumption type can be resolved from the referenced
- /// rule's += assignments, emits a cursor-typed guard
- /// ({prop}Cursor.Current is T) — declaring the cursor immediately into
- /// when not already present in
- /// . The cursor principle aligns the
- /// caller's guard with the position the called rule will actually consume from. When the
- /// type cannot be resolved or is not the top-level
- /// poco, the legacy {var}.{Property}.Count != 0 form is preserved.
+ /// Generates the inline condition for an optional non-terminal reference: a cursor-typed guard
+ /// ({prop}Cursor.Current is T, declaring the cursor when needed) so the guard matches the
+ /// position the called rule will actually consume from; falls back to the legacy
+ /// .Count != 0 form when the type cannot be resolved.
///
/// The used to emit any required cursor declarations
/// The optional non-terminal's referenced rule
@@ -419,17 +405,13 @@ private static string GenerateInlineOptionalCondition(EncodedTextWriter writer,
}
else if (referencedRule.QueryAllReferencedCollectionAssignments(propertyName, ruleGenerationContext.AllRules).Count > 0)
{
- // The referenced rule has += consumptions of this property but the cursor
- // types could not be resolved — fall back to the legacy collection-level
- // non-empty check rather than skip the clause entirely.
+ // += consumptions exist but their types could not be resolved — fall back to
+ // the legacy non-empty check rather than skip the clause.
conditionParts.Add($"{variableName}.{umlPropertyName}.Count != 0");
}
- // No += consumptions exist for this property anywhere in the referenced rule
- // tree (the property name reached the result set via a scalar `=` reference,
- // typically pulled in by a transitive non-terminal walk). Skipping the clause
- // tightens the guard to reflect what the called rule will actually consume
- // and avoids evaluating derived properties that the caller never reads.
+ // With no += consumptions at all (scalar `=` reach only), the clause is skipped so
+ // the guard reflects what the called rule actually consumes.
}
else
{
@@ -441,11 +423,9 @@ private static string GenerateInlineOptionalCondition(EncodedTextWriter writer,
}
///
- /// Resolves the runtime types the referenced rule's += assignments consume from the
- /// supplied and, when all are resolvable AND
- /// is poco, returns a cursor-typed boolean
- /// expression (declaring or reusing a cursor as needed). Returns null to signal
- /// that the caller should fall back to the legacy .Count != 0 form.
+ /// Returns a cursor-typed boolean expression for the referenced rule's += consumptions of
+ /// (declaring or reusing a cursor as needed), or null when the
+ /// caller should fall back to the legacy .Count != 0 form.
///
/// The used to emit a cursor declaration when one is required
/// The optional non-terminal's referenced rule
@@ -469,11 +449,8 @@ private static string TryBuildCursorTypedCheck(EncodedTextWriter writer, Textual
return null;
}
- // Each entry: (WrapperType, InnerType). InnerType is non-null when the assignment's
- // referenced rule is a "thin owning wrapper" (target=OwningMembership wrapping a
- // single ownedRelatedElement += T); the inner type T then provides the narrowing
- // discriminator that distinguishes the wrapper from sibling OwningMembership
- // subtypes (e.g. EndFeatureMembership) that share the cursor's collection.
+ // Inner is non-null for thin owning wrappers; the wrapped type is the discriminator that
+ // distinguishes the wrapper from sibling OwningMembership subtypes on the same cursor.
var resolvedTypes = new List<(string Wrapper, string Inner)>();
foreach (var assignmentElement in collectionAssignments)
@@ -507,13 +484,9 @@ private static string TryBuildCursorTypedCheck(EncodedTextWriter writer, Textual
}
///
- /// Builds a single cursor-typed boolean check. When is
- /// supplied the check narrows from a bare cursor.Current is Wrapper to
- /// (cursor.Current is Wrapper owningMembershipN && owningMembershipN.OwnedRelatedElement.OfType<Inner>().Any())
- /// so the discriminator is precise enough to distinguish a thin owning wrapper from
- /// sibling subtypes of the same wrapper class. The pattern-variable suffix is drawn from
- /// so multiple narrowed
- /// checks in the same generated method do not collide on the C# scope (CS0136).
+ /// Builds a single cursor-typed check; with it narrows to the
+ /// wrapped element type. The pattern-variable suffix comes from
+ /// to avoid CS0136 collisions.
///
/// The cursor variable name in scope at the emission site.
/// The fully-qualified wrapper type name.
@@ -534,12 +507,8 @@ private static string BuildTypeCheck(string cursorVariableName, string wrapperTy
}
///
- /// Returns the cursor variable name for , reusing an
- /// already-declared cursor when one is present in
- /// and emitting a new declaration into
- /// otherwise. The new declaration is registered in
- /// so subsequent host-rule elements
- /// targeting the same property reuse it.
+ /// Returns the cursor variable name for , reusing an existing cursor
+ /// or emitting and registering a new declaration.
///
/// The that receives the cursor declaration line when emitted
/// The property whose cursor is needed
diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs
index db018394..1fe71838 100644
--- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs
+++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs
@@ -105,13 +105,8 @@ internal void ProcessRuleElement(EncodedTextWriter writer, IClass umlClass, Rule
}
}
- // A repeated group followed by a MANDATORY consumption of the same element type
- // from the same cursor — the `( X )+ X` shape of e.g.
- // FeatureChainPrefix = ( ownedRelationship += OwnedFeatureChaining '.' )+
- // ownedRelationship += OwnedFeatureChaining '.'
- // — must leave one element for that trailing consumption. Without the
- // reservation the loop eats every element and the mandatory tail emits its
- // terminals against an exhausted cursor (`a.b.` became `a.b..`).
+ // A `( X )+ X` shape must leave one element for the mandatory tail, or the
+ // tail emits its terminals against an exhausted cursor (`a.b.` became `a.b..`).
var reservationGuard = ResolveTrailingConsumptionReservation(cursorToUse, umlClass, ruleGenerationContext);
if (groupTypeGuard.StartsWith("__FULL_GUARD__"))
@@ -258,16 +253,10 @@ internal void ProcessRuleElement(EncodedTextWriter writer, IClass umlClass, Rule
}
///
- /// Builds the extra while clause that reserves one element for a MANDATORY consumption
- /// following a repeated group, or an empty string when no reservation is needed.
- /// KEBNF rules of the shape ( prop += X )+ prop += X — FeatureChainPrefix being
- /// the canonical case — consume from a single shared cursor. Emitted naively the loop is greedy: it
- /// takes every element, and the mandatory trailing assignment then emits its terminals with nothing
- /// left to consume, duplicating them. The guard cursor.GetNext(1) is T stops the loop one
- /// element short, which is exactly the arity the grammar asks for.
- /// Applies only when the element immediately following the group in the SAME alternative is a
- /// non-optional += assignment drawing on the same cursor and the same sub-rule; any other
- /// successor consumes different elements and needs no reservation.
+ /// Builds the extra while clause reserving one element for a MANDATORY trailing consumption
+ /// after a repeated group (the ( prop += X )+ prop += X shape, e.g.
+ /// FeatureChainPrefix) — without it the greedy loop exhausts the cursor and the tail
+ /// duplicates its terminals. Empty when the successor is not a same-cursor += assignment.
///
/// The cursor the group consumes from.
/// The class hosting the current rule (provides the UML cache).
@@ -323,12 +312,9 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass
var previousCaller = ruleGenerationContext.CallerRule;
ruleGenerationContext.CallerRule = assignmentElement;
- // Route the cursor Move() through PendingCursorMove so that ProcessNonTerminalElement
- // emits it INSIDE the type-discrimination block — Move() then fires only when the
- // runtime cast actually matches (cursor advances only on real += consumption,
- // honouring the Move() ↔ += Golden Rule). When the assignment is inside a collection
- // group `(...)*` / `(...)+`, the loop body's own emitter handles the move; when it is
- // part of a multi-alternative dispatch, the dispatcher handles the move.
+ // Route Move() through PendingCursorMove so it lands INSIDE the type-discrimination
+ // block — the cursor advances only on real += consumption (Golden Rule). Collection
+ // groups and multi-alternative dispatchers emit their own move.
var shouldEmitCursorMove = !isPartOfMultipleAlternative
&& assignmentElement.Container is not GroupElement { IsCollection: true };
@@ -390,11 +376,8 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass
{
if (assignmentElement.Value is NonTerminalElement { Name: "REGULAR_COMMENT" })
{
- // Documentation rule (`doc /* … */`) surrounds the comment with
- // blank lines so doc blocks are visually separated from their
- // owning members. Every other rule that assigns a REGULAR_COMMENT
- // body (currently only `Comment`) renders adjacent to its
- // neighbouring statements per the SST convention.
+ // Only the Documentation rule separates its comment with blank lines;
+ // Comment renders adjacent per the SST convention.
var surroundWithBlankLines = string.Equals(ruleGenerationContext.NamedElementToGenerate?.Name, "Documentation", StringComparison.Ordinal);
writer.WriteSafeString($"SharedTextualNotationBuilder.AppendRegularComment(stringBuilder, poco.{targetPropertyName}, surroundWithBlankLines: {(surroundWithBlankLines ? "true" : "false")});");
}
@@ -404,10 +387,8 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass
}
else if (string.Equals(targetPropertyName, "Operator", StringComparison.Ordinal))
{
- // Operator tokens (binary, unary, conditional) need a trailing
- // space to separate them from the next operand. Matches the
- // convention used by the operator-switch dispatch path
- // (RuleProcessor.PatternHandlers.cs:94-95).
+ // Operator tokens need a trailing space before the next operand, matching
+ // the operator-switch dispatch path in RuleProcessor.PatternHandlers.cs.
writer.WriteSafeString($"stringBuilder.Append(poco.{targetPropertyName});{Environment.NewLine}");
writer.WriteSafeString("stringBuilder.Append(' ');");
}
@@ -422,13 +403,9 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass
{
if (!isPartOfMultipleAlternative && assignmentElement.Container is not GroupElement { IsOptional: true })
{
- // KEBNF `Prop ?= 'literal'` — emit the literal when the runtime
- // value is truthy, but suppress it for concrete subtypes whose
- // metamodel default already equals the literal-trigger value.
- // For those subtypes the keyword is structurally redundant and
- // the canonical idiomatic source omits it (see e.g. SysML
- // `attribute X` rather than `ref attribute X` because
- // AttributeUsage's `isReference` default is `true`).
+ // `Prop ?= 'literal'` — suppress the keyword for subtypes whose metamodel
+ // default already equals the trigger value (e.g. `attribute X`, not
+ // `ref attribute X`).
var exclusionTypes = targetProperty.QuerySubclassesWithMatchingDefault(umlClass, "true");
var exclusionClause = exclusionTypes.Count == 0
? string.Empty
@@ -463,13 +440,9 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass
var previousCaller = ruleGenerationContext.CallerRule;
ruleGenerationContext.CallerRule = nonTerminalElement;
- // Thin `[QualifiedName]` wrapper inlining: when the referenced rule's body is
- // just `[QualifiedName]` (e.g. FeatureReference, InstantiatedTypeReference) the
- // generated `Build{Wrapper}` method receives the target POCO as both target AND
- // source for name resolution, which loses the reference site. Inline the
- // AppendQualifiedName call here with the OUTER `poco` as the source context so
- // imports declared in the source's enclosing namespace can resolve to the
- // short / unqualified name.
+ // Thin `[QualifiedName]` wrappers are inlined with the OUTER `poco` as the
+ // resolution source — the generated Build{Wrapper} would lose the reference
+ // site (see IsThinQualifiedNameWrapperRule).
var referencedRule = ruleGenerationContext.FindRule(nonTerminalElement.Name);
if (IsThinQualifiedNameWrapperRule(referencedRule))
@@ -482,15 +455,9 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass
break;
}
- // Polymorphic `ownedMemberFeature` access on IFeatureMembership: the runtime
- // POCO may also be an IParameterMembership (the form used to model operands
- // of every InvocationExpression / OperatorExpression per KerML §8.2.5.8.2
- // Notes 1-2 — see Resources/KerML-textual-bnf.kebnf:1176-1178). In that
- // shape the operand expression lives under ownedMemberFeature → FeatureValue
- // → value rather than directly under ownedMemberFeature. Route the access
- // through SharedTextualNotationBuilder.QueryEffectiveOwnedMemberFeature
- // which transparently normalises both runtime shapes into a single feature
- // reference downstream code can type-test as before.
+ // On an IParameterMembership the operand lives under ownedMemberFeature →
+ // FeatureValue → value (KerML §8.2.5.8.2 Notes 1-2), so route the access
+ // through QueryEffectiveOwnedMemberFeature, which normalises both shapes.
if (string.Equals(targetProperty.Name, "ownedMemberFeature", StringComparison.Ordinal)
&& QueryIsAssignableToFeatureMembership(umlClass))
{
@@ -548,11 +515,8 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass
}
else
{
- // The grammar's assignment property does not resolve against the target
- // metamodel class (e.g. the OMG kebnf carries a one-off `ownedFeatureMember`
- // vs. metamodel `ownedMemberFeature` typo for `OwnedExpressionMember`).
- // Delegate to the HandCoded sibling per the documented convention rather
- // than emitting a name-collision-prone `Build{Property}(poco, …)` call.
+ // The grammar's property does not resolve against the metamodel class (e.g. the kebnf's
+ // `ownedFeatureMember` typo) — delegate to the HandCoded sibling.
var handCodedRuleName = assignmentElement.TextualNotationRule?.RuleName ?? "Unknown";
EmitHandCodedFallback(writer, handCodedRuleName, ruleGenerationContext);
}
@@ -649,13 +613,7 @@ internal void ProcessNonTerminalElement(EncodedTextWriter writer, IClass umlClas
}
else
{
- var previousCaller = ruleGenerationContext.CallerRule;
- ruleGenerationContext.CallerRule = nonTerminalElement;
- var previousName = ruleGenerationContext.CurrentVariableName;
-
- this.ProcessAlternatives(writer, umlClass, referencedRule?.Alternatives, ruleGenerationContext, isPartOfMultipleAlternative);
- ruleGenerationContext.CallerRule = previousCaller;
- ruleGenerationContext.CurrentVariableName = previousName;
+ this.ProcessReferencedRuleAlternatives(writer, umlClass, nonTerminalElement, referencedRule, ruleGenerationContext, isPartOfMultipleAlternative);
}
}
else
@@ -666,13 +624,7 @@ internal void ProcessNonTerminalElement(EncodedTextWriter writer, IClass umlClas
}
else
{
- var previousCaller = ruleGenerationContext.CallerRule;
- ruleGenerationContext.CallerRule = nonTerminalElement;
- var previousName = ruleGenerationContext.CurrentVariableName;
-
- this.ProcessAlternatives(writer, umlClass, referencedRule?.Alternatives, ruleGenerationContext, isPartOfMultipleAlternative);
- ruleGenerationContext.CallerRule = previousCaller;
- ruleGenerationContext.CurrentVariableName = previousName;
+ this.ProcessReferencedRuleAlternatives(writer, umlClass, nonTerminalElement, referencedRule, ruleGenerationContext, isPartOfMultipleAlternative);
}
}
}
@@ -702,6 +654,27 @@ internal void ProcessNonTerminalElement(EncodedTextWriter writer, IClass umlClas
}
}
+ ///
+ /// Recurses into 's alternatives with
+ /// as the caller rule, restoring the caller rule and current
+ /// variable name afterwards.
+ ///
+ /// The used to write output
+ /// The related
+ /// The referencing the rule
+ /// The referenced ; may be
+ /// The current
+ /// Whether this is part of a multi-alternative context
+ private void ProcessReferencedRuleAlternatives(EncodedTextWriter writer, IClass umlClass, NonTerminalElement callerElement, TextualNotationRule referencedRule, RuleGenerationContext ruleGenerationContext, bool isPartOfMultipleAlternative = false)
+ {
+ var previousCaller = ruleGenerationContext.CallerRule;
+ var previousName = ruleGenerationContext.CurrentVariableName;
+ ruleGenerationContext.CallerRule = callerElement;
+ this.ProcessAlternatives(writer, umlClass, referencedRule?.Alternatives, ruleGenerationContext, isPartOfMultipleAlternative);
+ ruleGenerationContext.CallerRule = previousCaller;
+ ruleGenerationContext.CurrentVariableName = previousName;
+ }
+
///
/// Declares a single cursor for an enumerable assignment property if not already declared.
///
@@ -786,9 +759,7 @@ private static void EmitSharedNoTargetRuleCall(EncodedTextWriter writer, IClass
///
/// Returns when IS-A
- /// FeatureMembership — used to gate the polymorphic ownedMemberFeature
- /// access path that normalises pure IFeatureMembership and
- /// IParameterMembership runtime shapes.
+ /// FeatureMembership — gates the polymorphic ownedMemberFeature access path.
///
/// The under test.
/// if the class IS-A FeatureMembership.
@@ -804,20 +775,11 @@ private static bool QueryIsAssignableToFeatureMembership(IClass umlClass)
}
///
- /// Returns when is a "thin
- /// [QualifiedName] wrapper" — i.e. its body is a single alternative containing a
- /// single element that is a [QualifiedName] resolution. Examples in the KerML
- /// grammar (Resources/KerML-textual-bnf.kebnf): FeatureReference : Feature =
- /// [QualifiedName] (line 1201) and InstantiatedTypeReference : Type =
- /// [QualifiedName] (line 1229).
- ///
- /// When a caller-rule's assignment-element references such a wrapper as its value, the
- /// generated Build{Wrapper} method receives the target as both target AND source
- /// for name resolution, which loses the syntactic reference site (the caller's
- /// poco) needed to honour imports declared in the source's enclosing scope chain.
- /// The codegen inlines the AppendQualifiedName call at the caller's emission point
- /// instead so the OUTER poco serves as the resolution source.
- ///
+ /// Returns when 's body is a single
+ /// [QualifiedName] resolution (e.g. FeatureReference,
+ /// InstantiatedTypeReference). Such wrappers are inlined at the caller so the OUTER
+ /// poco serves as the name-resolution source — the generated Build{Wrapper} would
+ /// receive the target as both target and source, losing the reference site.
///
/// The under test; may be .
/// if the rule is a thin [QualifiedName] wrapper.
diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs
index ccd32558..d5ae8e67 100644
--- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs
+++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs
@@ -222,14 +222,9 @@ private bool TryHandleEmptyVsNonEmptyMembership(EncodedTextWriter writer, IClass
var typeName = emptyTarget.QueryFullyQualifiedTypeName();
var cursorVarName = cursor.CursorVariableName;
- // An "Empty" wrapper rule usually still ASSIGNS its collection — e.g.
- // EmptyParameterMember : ParameterMembership = ownedRelatedElement += EmptyUsage, where
- // EmptyUsage : ReferenceUsage = {} contributes an element that simply emits nothing. For
- // such rules the collection is never empty at runtime, so a Count-based discriminator can
- // never select the empty branch (WhileLoopNode always emitted 'while', never 'loop').
- // Discriminate on the WRAPPED element type instead whenever the two branches wrap
- // different classes: the non-empty branch is the one that actually carries its payload
- // (an OwnedExpression), and the empty branch is the degenerate fallback.
+ // An "Empty" wrapper rule usually still ASSIGNS its collection (EmptyUsage = {}), so a
+ // Count-based discriminator can never select the empty branch. When the branches wrap
+ // different classes, discriminate on the WRAPPED element type instead.
var wrappedNonEmptyTypeName = QueryWrappedElementTypeName(nonEmptyBranch.NonTerminal, umlClass, ruleGenerationContext);
var wrappedEmptyTypeName = QueryWrappedElementTypeName(emptyBranch.NonTerminal, umlClass, ruleGenerationContext);
@@ -263,11 +258,9 @@ private bool TryHandleEmptyVsNonEmptyMembership(EncodedTextWriter writer, IClass
}
///
- /// Resolves the fully-qualified runtime type name of the element that a single-assignment
- /// wrapper rule (e.g. ExpressionParameterMember : ParameterMembership = ownedRelatedElement += OwnedExpression)
- /// puts into its collection — here IExpression. Returns when the
- /// referenced rule does not have exactly one += assignment of a non-terminal, or when the
- /// wrapped rule's target class cannot be resolved.
+ /// Resolves the runtime type a single-assignment wrapper rule puts into its collection
+ /// (e.g. IExpression for ExpressionParameterMember), or
+ /// when the rule does not have exactly one resolvable += non-terminal assignment.
///
/// The naming the wrapper rule.
/// The class hosting the current rule (provides the UML cache).
@@ -694,25 +687,10 @@ private void ProcessUnitypedAlternativesWithOneElement(EncodedTextWriter writer,
}
}
- // Subtype-overlap guard synthesis.
- //
- // After the existing IsValidFor pass, every duplicate group still has at most
- // one unguarded member that becomes the bare `case I{Target}:` fall-through.
- // That fall-through is only safe when no SIBLING alternative may dispatch a
- // subtype of I{Target} — otherwise the unguarded case greedily swallows the
- // subtype before it can reach the dispatcher that handles it (e.g. the
- // OperatorExpression group's unguarded ExtentExpression case swallowing
- // FeatureChainExpression before it can reach the sibling PrimaryExpression
- // alternative).
- //
- // Detection: the group has subtype overlap if any other alternative in the
- // dispatch targets a class that is a SUPERTYPE of this group's target — that
- // sibling's dispatcher may then handle subtypes of this group's target inside
- // its own switch.
- //
- // When detected, synthesise a `when` guard for the would-be-default member
- // from the rule's parsed body (only parsed assignments contribute; non-parsing
- // `{ … }` is ignored per GRAMMAR.md).
+ // Subtype-overlap guard synthesis: a duplicate group's unguarded fall-through case
+ // is only safe when no sibling alternative targets a SUPERTYPE of the group's class
+ // (whose dispatcher may handle this group's subtypes internally). When overlap is
+ // detected, synthesise a `when` guard from the rule's parsed body.
foreach (var duplicateGroup in duplicateClasses)
{
var stillUnguarded = duplicateGroup.Value
@@ -754,35 +732,12 @@ private void ProcessUnitypedAlternativesWithOneElement(EncodedTextWriter writer,
}
}
- // Self-default inheritance-ambiguity guard synthesis.
- //
- // The criterion: emit guards only when one of the switch's alternatives
- // has the same UML class as the rule's `NamedElementToGenerate`. That is
- // the "self-default" shape — the rule uses its own target class as one
- // alternative and that alternative becomes the default catch-all arm
- // for inline/non-named subclass forms. The canonical example is
- // FeatureElement: `FeatureElement : Feature = Feature | Step | Expression | …`
- // where `Feature` is both an alternative and the rule's target. An
- // inline IOperatorExpression (subclass of IExpression with no declared
- // name) reaching the switch would match `case IExpression` and be
- // mis-rendered; a property-derived guard like `DeclaredName != null`
- // makes that arm decline the match and fall through to `BuildFeature`.
- //
- // When the rule does NOT exhibit this self-default pattern
- // (BuildAnnotatingElement, BuildDefinitionElement, BuildNonFeatureElement
- // — `NamedElementToGenerate` is `Element`/`AnnotatingElement` but no
- // alternative targets that class), the most-derived-first ordering
- // alone is sufficient: every legitimate runtime metaclass maps to its
- // own arm. No guards are emitted, regardless of any speculative
- // metamodel inheritance overlap.
- //
- // For each non-default alternative under a self-default rule, run the
- // depth-aware synthesizer on the called rule's parsed body. Every
- // synthesized clause (with property-mismatch and scalar-on-`+=` filters
- // already applied) becomes part of the arm's `when` predicate, joined
- // by `&&`. Shared clauses across siblings are harmless — they filter
- // out subclass forms uniformly while runtime metaclass + most-derived
- // ordering still routes named instances to the correct concrete arm.
+ // Self-default guard synthesis: when the rule uses its own target class as one
+ // alternative (e.g. `FeatureElement : Feature = Feature | Step | …`), that arm is the
+ // catch-all for inline subclass forms — sibling arms need property-derived `when`
+ // guards (e.g. `DeclaredName != null`) so an anonymous subclass instance declines the
+ // match and falls through. Without the self-default shape, most-derived-first
+ // ordering alone suffices and no guards are emitted.
var generatingClassForSelfDefault = ruleGenerationContext.NamedElementToGenerate as IClass;
var isSelfDefault = generatingClassForSelfDefault != null
&& mappedNonTerminalElements.Any(element => element.UmlClass == generatingClassForSelfDefault);
@@ -1123,23 +1078,16 @@ private static void EmitCompoundPocoTypeBranch(EncodedTextWriter writer, IClass
}
///
- /// Synthesises a when-clause guard template for a duplicate-group member that
- /// would otherwise be emitted as the unguarded case I{Target}: fall-through, by
- /// walking the rule's parsed body and emitting one predicate per
- /// . Non-parsing { prop = X } assignments
- /// () are intentionally ignored — they are
- /// write-only side effects of parsing per SysML2.NET.CodeGenerator/GRAMMAR.md
- /// and must not influence dispatch.
+ /// Synthesises a when-guard template (with {0} as the case-variable placeholder)
+ /// from the rule's parsed body, one predicate per . Non-parsing
+ /// { prop = X } assignments are ignored per GRAMMAR.md. Returns null when the body
+ /// carries no usable parsed assignments.
///
/// The whose body to inspect
/// The duplicate group's target
/// The used to resolve referenced rule targets
/// All available rules for NonTerminal resolution
- ///
- /// A template (with {0} as the
- /// case variable name placeholder) ready for insertion into whenGuards, or
- /// null when the rule body carries no usable parsed assignments.
- ///
+ /// The guard template, or null.
private static string SynthesiseGuardFromRuleBody(TextualNotationRule rule, IClass targetClass, IXmiElementCache cache, IReadOnlyList allRules, int maxDepth = 0)
{
if (rule == null || targetClass == null)
@@ -1153,24 +1101,10 @@ private static string SynthesiseGuardFromRuleBody(TextualNotationRule rule, ICla
}
///
- /// Determines whether is itself a type-dispatcher — i.e.
- /// a rule whose body is exclusively a union of single-NonTerminal alternatives
- /// (Subclass1 | Subclass2 | …) that delegate to per-subclass builders.
- ///
- /// Used by the inheritance-ambiguity pass to suppress guard emission on a switch
- /// arm whose called rule already routes subclasses internally. For example, the
- /// LiteralExpression rule body is
- /// LiteralBoolean | LiteralInteger | LiteralRational | LiteralString | LiteralInfinity
- /// — any subclass that reaches a
- /// case ILiteralExpression arm is correctly dispatched to its concrete
- /// builder by BuildLiteralExpression's inner switch. Contrast with the
- /// Expression rule body
- /// FeaturePrefix 'expr' FeatureDeclaration ValuePart? FunctionBody: a
- /// concrete construction rule that only renders the base form, so a subclass
- /// (e.g. ) reaching a case IExpression
- /// arm would be mis-rendered as a standalone Expression declaration and DOES
- /// need a guard.
- ///
+ /// Determines whether 's body is exclusively a union of
+ /// single-NonTerminal alternatives (Subclass1 | Subclass2 | …). Such a rule routes
+ /// subclasses internally (e.g. LiteralExpression), so its switch arm needs no guard —
+ /// unlike a concrete construction rule that renders only the base form.
///
/// The rule to test, or null
/// true when is a multi-alternative type-dispatcher
@@ -1187,17 +1121,15 @@ private static bool IsTypeDispatcherRule(TextualNotationRule rule)
}
///
- /// Runs the structural-predicate walk for and returns the raw list
- /// of synthesised clauses, before they are joined with &&. Centralises the
- /// per-walk seeding of (visited-rules) and the cursor-state
- /// bookkeeping.
+ /// Runs the structural-predicate walk for and returns the raw clause
+ /// list, seeding the per-walk visited-rules set and cursor-state bookkeeping.
///
/// The entry to walk.
- /// The dispatch-time target (the alternative's UML class).
+ /// The dispatch-time target .
/// The for resolving NonTerminal RHS targets.
/// All available rules for NonTerminal resolution.
- /// Recursion budget: 0 = shallow (current/legacy behavior), N>0 = recurse up to NonTerminal references deep.
- /// The per-clause list (unjoined) in source-order.
+ /// Recursion budget: 0 = shallow, N>0 = recurse N references deep.
+ /// The per-clause list (unjoined) in source order.
private static List CollectGuardClausesForRule(TextualNotationRule rule, IClass targetClass, IXmiElementCache cache, IReadOnlyList allRules, int maxDepth)
{
var targetProperties = targetClass.QueryAllProperties();
@@ -1228,18 +1160,9 @@ private static List CollectGuardClausesForRule(TextualNotationRule rule,
}
///
- /// Combines per-grammar-alternative clause lists into a single OR-disjunction
- /// string, respecting grammar semantics: a rule body of the shape
- /// alt1 | alt2 | … means "exactly one of these paths is taken at parse
- /// time" → the corresponding dispatch-time predicate is the disjunction of the
- /// per-alternative AND-conjunctions.
- ///
- /// Each non-empty alternative's clause list is joined by &&
- /// (wrapped in parens when it has 2+ clauses to keep operator precedence
- /// explicit). Empty alternatives are dropped. Identical alternatives collapse
- /// to a single clause. When 2+ distinct alternatives remain, the overall result
- /// is wrapped in parens and joined by ||.
- ///
+ /// Combines per-alternative clause lists into one predicate: each alternative's clauses joined
+ /// by &&, alternatives joined by || (exactly one parses at runtime).
+ /// Empty alternatives are dropped and identical ones collapse.
///
/// One list of synthesized clauses per grammar alternative.
/// A single combined predicate string, or null when every alternative is empty.
@@ -1277,24 +1200,8 @@ private static string CombineAlternativesAsOr(List> perAlternativeC
/// The used to resolve referenced rule targets
/// All available rules for NonTerminal resolution
/// Accumulator into which non-null clauses are appended in source order
- ///
- /// Tracks whether a cursor predicate has already been emitted for an
- /// ownedRelationship += assignment in this rule. Only the first such
- /// assignment yields a cursor clause; subsequent ones run after the cursor has
- /// advanced and cannot be expressed at dispatch time.
- ///
- ///
- /// Tracks whether any element walked so far may have advanced the dispatch cursor
- /// (the ownedRelationship cursor) at runtime — either via a direct
- /// ownedRelationship += earlier in the body or via a NonTerminalElement
- /// reference whose target rule may consume ownedRelationship internally. When
- /// this becomes true, no subsequent ownedRelationship += … can yield a cursor
- /// predicate at dispatch time because cursor position 0 no longer corresponds to that
- /// assignment's target. Rules whose first += sits behind a
- /// NonTerminal?/NonTerminal* prefix (e.g. IndividualDefinition's
- /// trailing ownedRelationship += EmptyMultiplicityMember) therefore correctly
- /// produce no cursor clause and fall back to leaving the rule as the unguarded default.
- ///
+ /// Whether an ownedRelationship cursor predicate was already emitted — only the first assignment can be expressed at dispatch time
+ /// Whether a prior element may have advanced the dispatch cursor, invalidating position-0 predicates for the rest of the walk
private static void CollectGuardClauses(IEnumerable elements, IEnumerable targetProperties, IXmiElementCache cache, IReadOnlyList allRules, List clauses, ref bool firstCursorEmitted, ref bool cursorMayHaveAdvanced, HashSet visitedRules, int remainingDepth)
{
foreach (var element in elements)
@@ -1303,11 +1210,8 @@ private static void CollectGuardClauses(IEnumerable elements, IEnum
{
case AssignmentElement assignment:
{
- // Skip optional assignments (`?` / `*` suffix on the assignment
- // itself). Their target property may legitimately be unset on a
- // valid runtime POCO, so an emitted `&&`-joined predicate would
- // wrongly reject those instances at dispatch time. Only mandatory
- // assignments (no suffix or `+` suffix) yield guard clauses.
+ // Optional assignments may legitimately be unset on a valid POCO — only
+ // mandatory ones yield guard clauses.
if (assignment.IsOptional)
{
break;
@@ -1331,18 +1235,9 @@ private static void CollectGuardClauses(IEnumerable elements, IEnum
case NonTerminalElement nonTerminal:
{
- // Deep walk: if there is still depth budget and the referenced rule
- // has not yet been visited on this walk, descend into its body so
- // structural predicates further down the rule chain (e.g.
- // FeatureDeclaration → FeatureIdentification → declaredName = NAME)
- // are surfaced as additional clauses. Per-alternative OR-combine: a
- // referenced rule with multiple alternatives means exactly one path
- // fires at parse-time, so the dispatch-time predicate must be the
- // disjunction of the per-alternative AND-conjunctions (not their
- // conjunction). Once any NonTerminal is walked, the dispatch cursor
- // may have advanced (we cannot statically replay the entire builder),
- // so cursor-predicate emission is suppressed for the rest of this
- // walk via cursorMayHaveAdvanced = true.
+ // Deep walk while depth budget lasts: descend into unvisited referenced rules,
+ // OR-combining per-alternative clauses. Any walked NonTerminal may advance the
+ // dispatch cursor, so later cursor predicates are suppressed.
if (remainingDepth > 0)
{
var referencedRule = allRules.SingleOrDefault(rule => rule.RuleName == nonTerminal.Name);
@@ -1371,36 +1266,23 @@ private static void CollectGuardClauses(IEnumerable elements, IEnum
}
}
- // Shallow fallback: depth budget exhausted, rule not found, or
- // already visited. A NonTerminal reference may advance the dispatch
- // cursor at runtime, so conservatively suppress any later cursor
- // predicates.
+ // Depth exhausted, rule not found, or already visited — conservatively
+ // suppress later cursor predicates.
cursorMayHaveAdvanced = true;
break;
}
case GroupElement group:
{
- // Skip optional groups (`?` and `*` suffix). Assignments inside an
- // optional group are not guaranteed to fire at parse-time and
- // therefore the associated property may legitimately be unset on a
- // valid runtime POCO. Emitting them as mandatory `&&` clauses would
- // wrongly reject those POCOs at dispatch time. Only walk groups
- // that are guaranteed to execute: a group with no suffix or with
- // `+` (one-or-more — at least one iteration is mandatory).
+ // Optional groups may not fire at parse time — only walk groups guaranteed to
+ // execute (no suffix or `+`).
if (group.IsOptional)
{
break;
}
- // Per-alternative walk + OR-combine: each `group.Alternatives`
- // entry represents an "or-branch" of the group (`(a | b | c)`).
- // At runtime exactly one branch is taken, so the dispatch-time
- // predicate is the disjunction of per-branch AND-conjunctions.
- // Cursor-state (`firstCursorEmitted`, `cursorMayHaveAdvanced`) is
- // shared across branches because any branch may consume the
- // dispatch cursor; once that happens for any branch we must
- // conservatively suppress subsequent cursor predicates.
+ // OR-combine per branch; cursor state is shared across branches since any
+ // branch may consume the dispatch cursor.
var perGroupAlternativeClauses = new List>();
foreach (var groupAlternative in group.Alternatives)
@@ -1453,24 +1335,13 @@ private static string TryBuildClauseForAssignment(AssignmentElement assignment,
if (matchingProperty == null)
{
- // The assignment targets a property that does not exist on the outer
- // alternative's class. This can happen when the depth-aware walk descends
- // into a NonTerminal whose target metaclass is unrelated to the outer's
- // class (e.g. walking AnnotatingElement's switch dispatches into Comment
- // / Documentation rules that assign `body`, `locale`, `language` which
- // are NOT on the outer IAnnotatingElement). Emitting `{0}.{Prop}` would
- // produce a compile error since the case variable lacks that member.
- // Skip the clause; the outer guard remains correct without it.
+ // The deep walk can cross into rules whose properties do not exist on the OUTER class
+ // (e.g. Comment's `body`) — emitting `{0}.{Prop}` would not compile. Skip the clause.
return null;
}
- // Drop `+=` clauses whose target property is not actually a collection on the
- // outer class. The grammar's `+=` annotation tells the parser to append to a
- // collection in the called rule's target metaclass — but when the depth-aware
- // walk crosses a NonTerminal boundary, the OUTER class may expose the same
- // property name as a scalar (e.g. `ISubsetting.Specific` is a single IFeature,
- // not an IFeature collection). Emitting `{0}.Specific.OfType<…>().Any()` would
- // fail to compile against the scalar property.
+ // Likewise a `+=` property may be a SCALAR on the outer class (e.g. ISubsetting.Specific);
+ // an OfType<…>().Any() clause would not compile there.
if (assignment.Operator == "+=" && !matchingProperty.QueryIsEnumerable())
{
return null;
@@ -1490,14 +1361,8 @@ private static string TryBuildClauseForAssignment(AssignmentElement assignment,
}
///
- /// Determines whether can hold null in C#. True for
- /// reference-typed properties (POCO interfaces, ) and nullable
- /// value-typed properties (e.g. int?, VisibilityKind?); false for
- /// non-nullable value types (mandatory enums or primitives with multiplicity [1]).
- ///
- /// Used by to suppress tautological
- /// != null guards on properties that the runtime POCO cannot leave unset.
- ///
+ /// Determines whether can hold null in C# — used to suppress
+ /// tautological != null guards on non-nullable value types.
///
/// The resolved against the outer alternative class.
/// true if a runtime instance of the property could be null.
@@ -1509,17 +1374,9 @@ private static bool IsPropertyNullableInCSharp(IProperty property)
}
///
- /// Translates a parsed scalar = assignment into its corresponding
- /// when-clause predicate. Terminal-literal RHS becomes an equality check;
- /// [QualifiedName] RHS becomes a non-null check; NonTerminal RHS narrows to
- /// the referenced rule's target metaclass when known.
- ///
- /// When the matching property is a non-nullable value type (mandatory enum /
- /// primitive — multiplicity [1] in UML), != null and is I…
- /// predicates would be a tautology (always-true) or a compile error (value type
- /// vs reference type), so they are silently dropped. Only the terminal-equality
- /// branch ({0}.{Prop} == "literal") survives for non-nullable scalars.
- ///
+ /// Translates a scalar = assignment into a when predicate: literal RHS → equality,
+ /// [QualifiedName] → non-null, NonTerminal → type narrowing. Predicates that would be
+ /// tautological or uncompilable on non-nullable value types are dropped.
///
/// The with =
/// The on the outer alternative class that this assignment binds — consulted for C# nullability.
@@ -1532,13 +1389,9 @@ private static string BuildScalarAssignmentClause(AssignmentElement assignment,
switch (assignment.Value)
{
case TerminalElement terminal when !string.IsNullOrEmpty(terminal.Value):
- // Terminal-literal equality is meaningful for any property type.
return $"{{0}}.{propertyAccessor} == \"{terminal.Value}\"";
case ValueLiteralElement valueLiteral when valueLiteral.QueryIsQualifiedName():
- // [QualifiedName] RHS → non-null check; only meaningful when the C#
- // property can be null. Non-nullable value types (multiplicity [1]
- // enums / primitives) would yield a tautology — drop the clause.
return IsPropertyNullableInCSharp(matchingProperty)
? $"{{0}}.{propertyAccessor} != null"
: null;
@@ -1549,17 +1402,12 @@ private static string BuildScalarAssignmentClause(AssignmentElement assignment,
if (rhsTargetClass != null)
{
- // `is I{Rhs}` requires a reference-typed property. For non-nullable
- // value-typed properties (mandatory enums / primitives) the cast
- // would not compile, so drop the clause.
+ // `is I{Rhs}` needs a reference-typed property to compile.
return matchingProperty.QueryIsReferenceType()
? $"{{0}}.{propertyAccessor} is {rhsTargetClass.QueryFullyQualifiedTypeName()}"
: null;
}
- // Fallback when the NonTerminal does not resolve to an IClass (e.g.
- // its target is an enum literal or unresolved name) — emit `!= null`
- // only when the property can actually hold null.
return IsPropertyNullableInCSharp(matchingProperty)
? $"{{0}}.{propertyAccessor} != null"
: null;
@@ -1571,32 +1419,16 @@ private static string BuildScalarAssignmentClause(AssignmentElement assignment,
}
///
- /// Translates a parsed collection += assignment into a cursor-based
- /// when-clause predicate. The clause inspects the first element of the
- /// target collection via the project's standard cursor pattern
- /// (writerContext.CursorCache.GetOrCreateCursor(…).Current is …) — this
- /// keeps the dispatch-time guards consistent with how the rest of the textual
- /// notation builders consume containment collections.
- ///
- /// For ownedRelationship += the predicate is additionally gated by the
- /// outer walk's tracker: once any
- /// prior ownedRelationship += or a NonTerminal reference may have
- /// advanced the dispatch cursor past position 0, no further cursor predicate
- /// is emitted (it would check a stale position). For other collections that
- /// concern does not apply — each collection has its own independent cursor.
- ///
+ /// Translates a collection += assignment into a cursor-based when predicate
+ /// (GetOrCreateCursor(…).Current is …). For ownedRelationship the predicate is
+ /// suppressed once the dispatch cursor may have advanced past position 0.
///
- /// The with +=
- /// The C# collection-property name resolved via
+ /// The with operator +=
+ /// The resolved C# collection-property name
/// The for resolving NonTerminal RHS targets
/// All available rules for NonTerminal resolution
- /// Tracks whether the first ownedRelationship cursor predicate has been emitted yet for this walk.
- ///
- /// When true, the dispatch (ownedRelationship) cursor may have advanced
- /// past position 0 at runtime due to a prior ownedRelationship += or a
- /// preceding NonTerminal reference whose target rule may consume the
- /// cursor. Suppresses ownedRelationship cursor predicate emission.
- ///
+ /// Whether the first ownedRelationship cursor predicate was already emitted
+ /// When true, suppresses ownedRelationship cursor predicates (stale position)
/// A template clause, or null when no useful clause can be synthesised.
private static string BuildCollectionAssignmentClause(AssignmentElement assignment, string propertyAccessor, IXmiElementCache cache, IReadOnlyList allRules, ref bool firstCursorEmitted, bool cursorMayHaveAdvanced)
{
diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs
index e2215b7c..5a61fe88 100644
--- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs
+++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs
@@ -73,15 +73,36 @@ internal void ProcessAlternatives(EncodedTextWriter writer, IClass umlClass, IRe
/// The whose elements are emitted
/// The current
internal void EmitAlternativeBody(EncodedTextWriter writer, IClass umlClass, Alternatives alternative, RuleGenerationContext ruleGenerationContext)
+ {
+ this.EmitElements(writer, umlClass, alternative.Elements, ruleGenerationContext);
+ }
+
+ ///
+ /// Emits in order while maintaining the sibling/index context,
+ /// optionally restoring after each element.
+ ///
+ /// The used to write output
+ /// The current
+ /// The elements to emit
+ /// The current
+ /// Whether to restore the caller rule after each element
+ /// Whether this is part of a multi-alternative context
+ private void EmitElements(EncodedTextWriter writer, IClass umlClass, List elements, RuleGenerationContext ruleGenerationContext, bool restoreCallerPerElement = false, bool isPartOfMultipleAlternative = false)
{
var previousSiblings = ruleGenerationContext.CurrentSiblingElements;
var previousIndex = ruleGenerationContext.CurrentElementIndex;
- ruleGenerationContext.CurrentSiblingElements = alternative.Elements;
+ ruleGenerationContext.CurrentSiblingElements = elements;
- for (var elementIndex = 0; elementIndex < alternative.Elements.Count; elementIndex++)
+ for (var elementIndex = 0; elementIndex < elements.Count; elementIndex++)
{
ruleGenerationContext.CurrentElementIndex = elementIndex;
- this.ProcessRuleElement(writer, umlClass, alternative.Elements[elementIndex], ruleGenerationContext);
+ var previousCaller = ruleGenerationContext.CallerRule;
+ this.ProcessRuleElement(writer, umlClass, elements[elementIndex], ruleGenerationContext, isPartOfMultipleAlternative);
+
+ if (restoreCallerPerElement)
+ {
+ ruleGenerationContext.CallerRule = previousCaller;
+ }
}
ruleGenerationContext.CurrentSiblingElements = previousSiblings;
@@ -116,17 +137,13 @@ internal void DeclareAllRequiredCursors(EncodedTextWriter writer, IClass umlClas
}
///
- /// Emits a Build{ruleName}HandCoded(variable, writerContext, stringBuilder); fallback call.
- /// When is true, the call is only emitted if it has not
- /// already been emitted for the same rule name in the current generation scope.
+ /// Emits a Build{ruleName}HandCoded(…) fallback call, optionally deduplicated per
+ /// generation scope.
///
/// The used to write output
/// The grammar rule name used to form the method name
/// The current
- ///
- /// When true, suppress duplicate emissions via
- ///
- ///
+ /// When true, suppress duplicate emissions for the same rule name
private static void EmitHandCodedFallback(EncodedTextWriter writer, string ruleName, RuleGenerationContext ruleGenerationContext, bool deduplicate = false)
{
if (deduplicate && !ruleGenerationContext.EmittedHandCodedCalls.Add(ruleName))
@@ -138,21 +155,10 @@ private static void EmitHandCodedFallback(EncodedTextWriter writer, string ruleN
}
///
- /// Collects, in the order they will be consumed at runtime, the +=
- /// items that target .
- /// The collected items are: first the += assignments declared inside the optional
- /// group itself (), then the +=
- /// assignments declared by the parent alternative AFTER
- /// ().
- ///
- /// The tail walk stops as soon as it encounters a sibling whose own contribution to the
- /// target cursor is not statically determinable (an optional or collection
- /// that nests a += on the same property, or a
- /// that could indirectly consume the cursor). When the
- /// tail offset is uncertain, only the optional group's own consumptions are returned —
- /// callers can then emit a guard that type-checks the optional positions without making
- /// claims about the tail.
- ///
+ /// Collects, in runtime consumption order, the += assignments targeting
+ /// : the optional group's own, then the parent
+ /// alternative's tail. The tail walk stops at the first sibling whose cursor contribution is
+ /// not statically determinable, so callers never guard positions they cannot prove.
///
/// The optional group's own elements
/// The parent alternative's elements
@@ -209,10 +215,8 @@ when string.Equals(cursorAssignment.Property, targetPropertyName, StringComparis
}
///
- /// Determines whether the supplied (recursively) contains
- /// any += targeting .
- /// Used by to decide whether a sibling group
- /// could shift the runtime offset of subsequent cursor consumptions.
+ /// Determines recursively whether contains a += assignment
+ /// targeting (i.e. could shift the runtime cursor offset).
///
/// The to inspect
/// The property name whose consumption is detected
@@ -239,11 +243,8 @@ when string.Equals(cursorAssignment.Property, targetPropertyName, StringComparis
}
///
- /// Resolves the fully-qualified runtime type name (e.g.
- /// SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership) that an
- /// 's consumed cursor element is expected to satisfy.
- /// Returns null when the assignment's value is not a
- /// or when the referenced rule has no resolvable target class.
+ /// Resolves the fully-qualified runtime type the assignment's consumed cursor element must
+ /// satisfy, or null when the referenced rule has no resolvable target class.
///
/// The += assignment to resolve the target type of
/// The class hosting the current rule (provides the UML cache)
@@ -269,17 +270,10 @@ private static string ResolveAssignmentTargetTypeName(AssignmentElement assignme
}
///
- /// Resolves the fully-qualified runtime type name of the inner element a "thin owning
- /// wrapper" rule wraps. A thin owning wrapper is a rule whose target is
- /// OwningMembership and whose body is a single
- /// ownedRelatedElement += SomeNonTerminal assignment (e.g.
- /// OwnedMultiplicity : OwningMembership = ownedRelatedElement += MultiplicityRange).
- /// In such cases the wrapper type (IOwningMembership) is too coarse a discriminator
- /// because every OwningMembership subtype (e.g. EndFeatureMembership) also
- /// satisfies it; narrowing the check to the wrapped inner element type
- /// (IMultiplicityRange) gives the precision the optional-group guard needs.
- /// Returns when the assignment's referenced rule does not match
- /// the thin-wrapper shape.
+ /// Resolves the inner element type of a "thin owning wrapper" rule
+ /// (X : OwningMembership = ownedRelatedElement += Y). The wrapper type is too coarse a
+ /// discriminator — every OwningMembership subtype satisfies it — so the guard narrows to
+ /// the wrapped type. Returns when the rule is not a thin wrapper.
///
/// The += assignment whose referenced rule is inspected.
/// The class hosting the current rule (provides the UML cache).
@@ -387,12 +381,9 @@ private void ProcessSingleAlternative(EncodedTextWriter writer, IClass umlClass,
}
else
{
- // Guard on the TYPE the assignment consumes, not merely on the cursor being
- // non-empty. An optional group is entered only when the element it would
- // consume is actually present; a bare non-null test also passes for the next
- // UNRELATED relationship, emitting the group's terminals spuriously — e.g.
- // AcceptParameterPart's ( 'via' ownedRelationship += NodeParameterMember )?
- // emitted `via` whenever the accept action merely had a body to follow.
+ // Guard on the TYPE the assignment consumes, not on mere cursor non-emptiness —
+ // a bare non-null test also passes for the next UNRELATED relationship and emits
+ // the group's terminals spuriously (e.g. AcceptParameterPart's `via`).
var singleTypeName = ResolveAssignmentTargetTypeName(assigment, umlClass, ruleGenerationContext);
ifStatementContent.Add(singleTypeName == null
@@ -405,12 +396,9 @@ private void ProcessSingleAlternative(EncodedTextWriter writer, IClass umlClass,
{
var condition = property.QueryIfStatementContentForNonEmpty("poco");
- // For `Prop ?= 'literal'` keyword assignments inside an optional `(...)?`
- // group, exclude concrete subtypes whose metamodel default for the
- // assigned property already equals the literal-trigger value — the
- // keyword is structurally redundant for those subtypes and the
- // canonical source omits it (e.g. `attribute X` rather than
- // `ref attribute X` because AttributeUsage::isReference defaults to true).
+ // For `Prop ?= 'literal'` in an optional group, exclude subtypes whose metamodel
+ // default already equals the trigger value — the keyword is redundant there
+ // (e.g. `attribute X`, not `ref attribute X`).
if (property.QueryIsBool())
{
var exclusionTypes = property.QuerySubclassesWithMatchingDefault(umlClass, "true");
@@ -420,16 +408,9 @@ private void ProcessSingleAlternative(EncodedTextWriter writer, IClass umlClass,
condition += $" && poco is not ({string.Join(" or ", exclusionTypes.Select(c => c.QueryFullyQualifiedTypeName()))})";
}
- // A DERIVED property cannot record whether the keyword was written: its runtime
- // value is COMPUTED from other state, so it is routinely true for reasons that
- // have nothing to do with the notation. `Usage::isReference` derives as
- // `not isComposite`, and every context in which the metamodel FORCES a Usage to
- // be referential (validateUsageIsReferential — directed, end feature, or no
- // featuringType) makes it true while the canonical source carries no `ref` at
- // all. The type-level exclusion above cannot see that: the redundancy is
- // per-INSTANCE, not per-subclass. Delegate the instance-level decision to a
- // hand-coded guard companion, reusing the `IsValidFor…` convention that the
- // rule-alternative dispatch already relies on.
+ // A DERIVED property cannot record whether the keyword was written (e.g.
+ // Usage::isReference = not isComposite is true in contexts with no `ref` at all),
+ // and the redundancy is per-INSTANCE — delegate to a hand-coded IsValidFor… guard.
if (property.IsDerived || property.IsDerivedUnion)
{
condition += $" && poco.IsValidFor{ruleGenerationContext.NamedElementToGenerate?.Name}{property.Name.CapitalizeFirstLetter()}(writerContext)";
@@ -444,20 +425,7 @@ private void ProcessSingleAlternative(EncodedTextWriter writer, IClass umlClass,
writer.WriteSafeString($"){Environment.NewLine}");
writer.WriteSafeString($"{{{Environment.NewLine}");
- var previousSiblings = ruleGenerationContext.CurrentSiblingElements;
- var previousIndex = ruleGenerationContext.CurrentElementIndex;
- ruleGenerationContext.CurrentSiblingElements = elements;
-
- for (var elementIndex = 0; elementIndex < elements.Count; elementIndex++)
- {
- ruleGenerationContext.CurrentElementIndex = elementIndex;
- var previousCaller = ruleGenerationContext.CallerRule;
- this.ProcessRuleElement(writer, umlClass, elements[elementIndex], ruleGenerationContext);
- ruleGenerationContext.CallerRule = previousCaller;
- }
-
- ruleGenerationContext.CurrentSiblingElements = previousSiblings;
- ruleGenerationContext.CurrentElementIndex = previousIndex;
+ this.EmitElements(writer, umlClass, elements, ruleGenerationContext, restoreCallerPerElement: true);
}
else
{
@@ -490,18 +458,7 @@ private void ProcessSingleAlternative(EncodedTextWriter writer, IClass umlClass,
writer.WriteSafeString($"{{{Environment.NewLine}");
- var previousSiblings = ruleGenerationContext.CurrentSiblingElements;
- var previousIndex = ruleGenerationContext.CurrentElementIndex;
- ruleGenerationContext.CurrentSiblingElements = elements;
-
- for (var elementIndex = 0; elementIndex < elements.Count; elementIndex++)
- {
- ruleGenerationContext.CurrentElementIndex = elementIndex;
- this.ProcessRuleElement(writer, umlClass, elements[elementIndex], ruleGenerationContext);
- }
-
- ruleGenerationContext.CurrentSiblingElements = previousSiblings;
- ruleGenerationContext.CurrentElementIndex = previousIndex;
+ this.EmitElements(writer, umlClass, elements, ruleGenerationContext);
}
if (!ruleGenerationContext.IsNextElementNewLineTerminal() && !ruleGenerationContext.IsLastElement())
@@ -513,20 +470,7 @@ private void ProcessSingleAlternative(EncodedTextWriter writer, IClass umlClass,
}
else
{
- var previousSiblings = ruleGenerationContext.CurrentSiblingElements;
- var previousIndex = ruleGenerationContext.CurrentElementIndex;
- ruleGenerationContext.CurrentSiblingElements = elements;
-
- for (var elementIndex = 0; elementIndex < elements.Count; elementIndex++)
- {
- ruleGenerationContext.CurrentElementIndex = elementIndex;
- var previousCaller = ruleGenerationContext.CallerRule;
- this.ProcessRuleElement(writer, umlClass, elements[elementIndex], ruleGenerationContext, isPartOfMultipleAlternative);
- ruleGenerationContext.CallerRule = previousCaller;
- }
-
- ruleGenerationContext.CurrentSiblingElements = previousSiblings;
- ruleGenerationContext.CurrentElementIndex = previousIndex;
+ this.EmitElements(writer, umlClass, elements, ruleGenerationContext, restoreCallerPerElement: true, isPartOfMultipleAlternative);
}
}
@@ -742,12 +686,7 @@ private void EmitNonTerminalThenAssignmentDispatch(EncodedTextWriter writer, ICl
}
else
{
- var previousCaller = ruleGenerationContext.CallerRule;
- var previousName = ruleGenerationContext.CurrentVariableName;
- ruleGenerationContext.CallerRule = nonTerminalElement;
- this.ProcessAlternatives(writer, umlClass, nonTerminalReferencedRule?.Alternatives, ruleGenerationContext);
- ruleGenerationContext.CallerRule = previousCaller;
- ruleGenerationContext.CurrentVariableName = previousName;
+ this.ProcessReferencedRuleAlternatives(writer, umlClass, nonTerminalElement, nonTerminalReferencedRule, ruleGenerationContext);
}
writer.WriteSafeString($"{Environment.NewLine}}}{Environment.NewLine}");
@@ -964,26 +903,10 @@ private static bool TryEmitQualifiedNameOrChainAlternatives(EncodedTextWriter wr
writer.WriteSafeString($"{{{Environment.NewLine}");
writer.WriteSafeString($"SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder,{variableName}.{resolvedPropertyName}, writerContext, poco);{Environment.NewLine}");
- // The two alternatives denote the SAME notational prefix, so the reference form must be
- // terminated the same way the chain form is. When the chain rule ends in a terminal — as
- // FeatureChainPrefix does with its trailing '.' — that terminal belongs to the prefix notation
- // rather than to the chain, so the [QualifiedName] branch has to emit it too.
- //
- // Ground truth is the reference parser, whose FlowEndSubsetting spells the terminal out on BOTH
- // alternatives (org.omg.sysml.xtext/src/org/omg/sysml/xtext/SysML.xtext and the identical KerML
- // production in org.omg.kerml.xtext/src/org/omg/kerml/xtext/KerML.xtext):
- //
- // FlowEndSubsetting returns SysML::ReferenceSubsetting :
- // referencedFeature = [SysML::Feature | QualifiedName] '.'
- // | ownedRelatedElement += FeatureChainPrefix
- //
- // Resources/SysML-textual-bnf.kebnf omits that '.' on the reference alternative — the only
- // genuine missing terminal found when diffing all 403 shared rules against the reference
- // grammar. Since the kebnf files are OMG-owned and immutable, the terminal is recovered here
- // instead. The inference is sound because the correlation holds for every rule of this shape:
- // OwnedFeatureTyping, OwnedSubsetting, OwnedReferenceSubsetting, OwnedCrossSubsetting and
- // OwnedRedefinition all pair with OwnedFeatureChain, which ends in a group and therefore keeps
- // the plain space separator; FlowEndSubsetting is the sole rule pairing with FeatureChainPrefix.
+ // Both alternatives denote the same notational prefix, so when the chain rule ends in a
+ // terminal (FeatureChainPrefix's trailing '.') the [QualifiedName] branch must emit it too.
+ // The kebnf omits that '.' on the reference alternative of FlowEndSubsetting; the pilot's
+ // Xtext grammar spells it out on both, and the kebnf is immutable, so it is recovered here.
var chainTrailingTerminal = QueryTrailingTerminal(referencedRule);
if (chainTrailingTerminal == null)
@@ -1002,10 +925,8 @@ private static bool TryEmitQualifiedNameOrChainAlternatives(EncodedTextWriter wr
}
///
- /// Returns the value of the that ends
- /// with, or when the rule has more than one alternative or does not end
- /// in a terminal. Used to keep alternative forms of the same notational construct terminated
- /// identically — see .
+ /// Returns the terminal value ends with, or when
+ /// it has multiple alternatives or does not end in a terminal.
///
/// The referenced ; may be .
/// The trailing terminal's value, or .
@@ -1024,17 +945,10 @@ private static string QueryTrailingTerminal(TextualNotationRule rule)
}
///
- /// Attempts to emit code for the subclass-rule dispatch pattern:
- /// property = X | SubclassRule, where SubclassRule targets a strict
- /// specialization of the current rule's target metaclass (e.g.
- /// FeatureChainMember : Membership = memberElement = [QualifiedName] | OwnedFeatureChainMember
- /// with OwnedFeatureChainMember : OwningMembership). The runtime subtype is the
- /// discriminator: only an instance of the subclass can be the subclass-rule alternative,
- /// so the emitted code dispatches on the POCO's runtime type FIRST and only falls back to
- /// the assignment alternative for base-class instances. Emitting the alternatives in
- /// grammar order instead would put a derived-property null check (e.g.
- /// MemberElement != null, never null on an OwningMembership) in front,
- /// rendering the subclass alternative unreachable.
+ /// Attempts the subclass-rule dispatch pattern property = X | SubclassRule, where
+ /// SubclassRule targets a strict specialization of the current metaclass (e.g.
+ /// FeatureChainMember). Dispatches on the runtime subtype FIRST — grammar order would put
+ /// a never-null derived-property check in front and make the subclass alternative unreachable.
///
/// The used to write output
/// The related
@@ -1115,17 +1029,10 @@ private bool TryEmitSubclassRuleDispatchAlternatives(EncodedTextWriter writer, I
}
///
- /// Attempts to emit code for the single-element-or-same-class-rule pattern:
- /// collection += X | SameClassRule, where SameClassRule targets the current
- /// rule's own metaclass and re-consumes the same collection property (e.g.
- /// ChainingPart : Feature = 'chains' (ownedRelationship += OwnedFeatureChaining | FeatureChain)
- /// with FeatureChain : Feature = ownedRelationship += OwnedFeatureChaining ('.' ownedRelationship += OwnedFeatureChaining)+).
- /// Because the same-class rule consumes two or more elements of the += value type,
- /// the discriminator is the element count: exactly one matching element selects the single
- /// += alternative (with its Golden-Rule Move()), otherwise the same-class
- /// rule is delegated to and manages the shared cursor itself. A bare
- /// cursor.Current != null discriminator would make the same-class alternative
- /// unreachable and, without the Move(), stall the caller's dispatch loop.
+ /// Attempts the pattern collection += X | SameClassRule, where the same-class rule
+ /// re-consumes the same collection (e.g. ChainingPart vs FeatureChain). The
+ /// discriminator is the element COUNT: exactly one match selects the single += alternative
+ /// (with its Move()); otherwise the same-class rule manages the shared cursor itself.
///
/// The used to write output
/// The related
@@ -1162,11 +1069,8 @@ private bool TryEmitSingleElementOrSameClassRuleAlternatives(EncodedTextWriter w
return false;
}
- // The same-class rule must re-consume the same collection property THROUGH THE SAME
- // sub-rule (e.g. FeatureChain re-consumes ownedRelationship += OwnedFeatureChaining):
- // only then do the two alternatives compete for the same element type and need the
- // count discriminator. Disjoint element types (e.g. CalculationBodyItem's
- // ReturnParameterMember vs ActionBodyItem) stay with the plain type dispatch.
+ // Only when the same-class rule re-consumes the same property THROUGH THE SAME sub-rule do
+ // the alternatives compete for one element type and need the count discriminator.
var elementValueNonTerminal = (NonTerminalElement)assignmentElement.Value;
var reconsumesSameElements = referencedRule.Alternatives
@@ -1346,14 +1250,9 @@ private void EmitTerminalVsBodyWithCollectionNonTerminals(EncodedTextWriter writ
var propertyAccessName = targetProperty.QueryPropertyNameBasedOnUmlProperties();
- // KEBNF `XBody : Type = ';' | '{' XBodyItem* '}'` — both the choice and the `*` loop
- // are bounded by "does the current cursor element match an XBodyItem alternative?".
- // For body item rules whose dispatcher can encounter unrecognised elements legitimately
- // belonging to a parent rule (notably PortDefinition's trailing
- // ConjugatedPortDefinitionMember, which appears in OwnedRelationship but is NOT a
- // DefinitionBodyItem alternative), the body rule must defer to an
- // `IsValidFor{XBodyItem}` predicate. Other body rules retain the simple
- // `cursor.Current != null` semantics; we promote rules into the guarded form by name.
+ // For body item rules that can encounter elements legitimately belonging to a parent rule
+ // (e.g. PortDefinition's trailing ConjugatedPortDefinitionMember), the `;` choice and the
+ // `*` loop must defer to an IsValidFor{XBodyItem} predicate instead of a bare non-null test.
var requiresIsValidForGuard = IsGuardedBodyItemRule(collectionNonTerminals[0].Name);
var guardCallSuffix = requiresIsValidForGuard
? $".IsValidFor{collectionNonTerminals[0].Name}(writerContext)"
@@ -1407,12 +1306,7 @@ private void EmitTerminalVsBodyWithCollectionNonTerminals(EncodedTextWriter writ
}
else
{
- var previousCaller = ruleGenerationContext.CallerRule;
- var previousName = ruleGenerationContext.CurrentVariableName;
- ruleGenerationContext.CallerRule = collectionNonTerminal;
- this.ProcessAlternatives(writer, umlClass, referencedRule?.Alternatives, ruleGenerationContext);
- ruleGenerationContext.CallerRule = previousCaller;
- ruleGenerationContext.CurrentVariableName = previousName;
+ this.ProcessReferencedRuleAlternatives(writer, umlClass, collectionNonTerminal, referencedRule, ruleGenerationContext);
}
writer.WriteSafeString($"{Environment.NewLine}}}{Environment.NewLine}");
@@ -1495,12 +1389,7 @@ private void EmitTerminalVsBodyWithSingleNonTerminal(EncodedTextWriter writer, I
}
else
{
- var previousCaller = ruleGenerationContext.CallerRule;
- var previousName = ruleGenerationContext.CurrentVariableName;
- ruleGenerationContext.CallerRule = singleNonTerminal;
- this.ProcessAlternatives(writer, umlClass, referencedRule?.Alternatives, ruleGenerationContext);
- ruleGenerationContext.CallerRule = previousCaller;
- ruleGenerationContext.CurrentVariableName = previousName;
+ this.ProcessReferencedRuleAlternatives(writer, umlClass, singleNonTerminal, referencedRule, ruleGenerationContext);
}
writer.WriteSafeString($"{Environment.NewLine}}}{Environment.NewLine}");
@@ -1515,19 +1404,10 @@ private void EmitTerminalVsBodyWithSingleNonTerminal(EncodedTextWriter writer, I
}
///
- /// Returns true when the KEBNF body-item rule named can have
- /// elements ahead of the cursor that legitimately belong to a parent rule (and therefore must
- /// NOT be consumed by the body's * loop). For these rules,
- /// emits the ; / { … } choice and the * loop as
- /// IsValidFor{XBodyItem}-guarded code rather than a raw cursor.Current != null
- /// check, faithfully implementing the KEBNF * quantifier semantics ("iterate while
- /// the current element matches an alternative") for the affected rules.
- /// Allowlist (rather than allow-all) keeps the codegen change scoped: only rules whose
- /// dispatcher can encounter foreign elements need the guard. Currently this is the two
- /// rules whose body item includes the DefinitionMember alternative — DefinitionBodyItem
- /// (consumed by PortDefinition's trailing ConjugatedPortDefinitionMember that
- /// the body must skip) and InterfaceBodyItem (same shape). All other body item rules
- /// retain the existing cursor.Current != null semantics.
+ /// Returns true when the body-item rule can have cursor elements that legitimately belong to a
+ /// parent rule and must not be consumed by the body's * loop. Allowlisted by name to keep
+ /// the guarded form scoped: currently DefinitionBodyItem (PortDefinition's trailing
+ /// ConjugatedPortDefinitionMember) and InterfaceBodyItem.
///
/// The KEBNF rule name of the body item (e.g. DefinitionBodyItem)
/// true if the codegen should emit the guarded form
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/04-Functional Allocation/4a-Functional Allocation.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/04-Functional Allocation/4a-Functional Allocation.sysml
new file mode 100644
index 00000000..4789b46c
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/04-Functional Allocation/4a-Functional Allocation.sysml
@@ -0,0 +1,80 @@
+package '4a-Functional Allocation' {
+ private import '2a-Parts Interconnection'::*;
+ private import '3a-Function-based Behavior-1'::*;
+ private import '3a-Function-based Behavior-1'::'provide power'::*;
+ part vehicle1_c1_functional_allocation :> vehicle1_c1 {
+ port :>> fuelCmdPort {
+ in fuelCmd: FuelCmd;
+ }
+ perform 'provide power' {
+ doc
+ /*
+ * This allocates the action '3a-Function-based Behavior-1'::'provide power' as an enacted
+ * performance of 'vehicle_c1_functional_allocation'.
+ */
+
+ in fuelCmd = fuelCmdPort.fuelCmd;
+ }
+ part :>> engine {
+ port :>> fuelCmdPort {
+ in fuelCmd: FuelCmd;
+ }
+ perform 'provide power'.'generate torque' {
+ /*
+ * This allocates one of the sub-steps of 'provide power' to a sub-part of vehicle_c1.
+ */
+ in fuelCmd = fuelCmdPort.fuelCmd;
+ out engineTorque = drivePwrPort.engineTorque;
+ }
+ port :>> drivePwrPort {
+ out engineTorque: Torque;
+ }
+ }
+ part :>> transmission {
+ port :>> clutchPort {
+ in attribute engineTorque: Torque;
+ }
+ perform 'provide power'.'amplify torque' {
+ in engineTorque = clutchPort.engineTorque;
+ out transmissionTorque = shaftPort_a.transmissionTorque;
+ }
+ port :>> shaftPort_a {
+ out transmissionTorque: Torque;
+ }
+ }
+ part :>> driveshaft {
+ port :>> shaftPort_b {
+ in transmissionTorque: Torque;
+ }
+ perform 'provide power'.'transfer torque' {
+ in transmissionTorque = shaftPort_b.transmissionTorque;
+ out driveshaftTorque = shaftPort_c.driveshaftTorque;
+ }
+ port :>> shaftPort_c {
+ out driveshaftTorque: Torque;
+ }
+ }
+ part :>> rearAxleAssembly {
+ port :>> shaftPort_d {
+ in driveshaftTorque: Torque;
+ }
+ perform 'provide power'.'distribute torque' {
+ in driveshaftTorque = shaftPort_d.driveshaftTorque;
+ out wheelTorque1 = rearAxle.leftHalfAxle.axleToWheelPort.wheelTorque;
+ out wheelTorque2 = rearAxle.rightHalfAxle.axleToWheelPort.wheelTorque;
+ }
+ part :>> rearAxle {
+ part :>> leftHalfAxle {
+ port :>> axleToWheelPort {
+ out wheelTorque: Torque;
+ }
+ }
+ part :>> rightHalfAxle {
+ port :>> axleToWheelPort {
+ out wheelTorque: Torque;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/SysML2.NET.Serializer.TextualNotation.Tests.csproj b/SysML2.NET.Serializer.TextualNotation.Tests/SysML2.NET.Serializer.TextualNotation.Tests.csproj
index 254f7640..52805693 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/SysML2.NET.Serializer.TextualNotation.Tests.csproj
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/SysML2.NET.Serializer.TextualNotation.Tests.csproj
@@ -130,6 +130,12 @@
Always
+
+ Always
+
+
+ Always
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/02-Parts Interconnection/2a-Parts Interconnection.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/02-Parts Interconnection/2a-Parts Interconnection.sysmlx
index 5decf171..42188965 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/02-Parts Interconnection/2a-Parts Interconnection.sysmlx
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/02-Parts Interconnection/2a-Parts Interconnection.sysmlx
@@ -1,374 +1,374 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
@@ -378,609 +378,609 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+