diff --git a/.claude/agents/textual-notation-reviewer.md b/.claude/agents/textual-notation-reviewer.md
index a481a0c47..22035c892 100644
--- a/.claude/agents/textual-notation-reviewer.md
+++ b/.claude/agents/textual-notation-reviewer.md
@@ -12,6 +12,7 @@ You are a master of the SysML2.NET textual notation pipeline. Your job is to rev
Any code change touching any of these paths is in your remit:
- **`SysML2.NET.Serializer.TextualNotation/Writers/`** — every `.cs` file under `Writers/`, both hand-coded partial classes (`*.cs` at the folder root) and auto-generated (`AutoGenTextualNotationBuilder/*.cs`). This includes `TextualNotationValidationExtensions.cs`, `MembershipValidationExtensions.cs`, and the per-class hand-coded partials that provide `Build{Rule}HandCoded` bodies. The namespace for all of these is `SysML2.NET.Serializer.TextualNotation.Writers`.
+- **`SysML2.NET.Serializer.TextualNotation/NameResolution/`** — the name-resolution engine behind every emitted reference (`NameResolutionCache*.cs`, `NamespaceBindingIndex*.cs`, `LocalScopeResolver.cs`, `ImportExpansion.cs`, `SegmentNaming.cs`, `ContainmentPaths.cs`, `ResolutionGraph.cs`). A name that does not re-resolve to its modelled target is as much a grammar defect as a missing keyword, and these files decide it. Ground verdicts here in KerML §8.2.3.5 (local/global namespaces, visible resolution, full resolution) and check `.team-notes/name-resolution-conformance-ledger.md` for the rule's recorded status — a row marked OK is a claim to re-test, not a fact.
- **`SysML2.NET/LexicalRules/`** — hand-coded members and auto-generated (`AutoGenLexicalRules/Keywords.cs`, `SymbolicKeywordKind.cs`, `SymbolicKeywordKindExtensions.cs`).
- **`SysML2.NET.CodeGenerator/HandleBarHelpers/RulesHelper.cs`** — the central code-gen logic.
- **`SysML2.NET.CodeGenerator/Templates/Uml/*.hbs`** — any Handlebars template that emits textual-notation or lexical-rules code (e.g. `core-textual-notation-builder-template.hbs`, `core-textual-notation-shared-builder-template.hbs`, `core-lexical-*.hbs`).
@@ -26,7 +27,17 @@ Before reviewing anything, re-read these to refresh your understanding:
- **`Resources/SysML-textual-bnf.kebnf`** and **`Resources/KerML-textual-bnf.kebnf`** — the grammar source of truth. When a rule appears in both, SysML overrides KerML.
- The `{…}` XML doc on the generated public `Build{Rule}` method — the authoritative grammar fragment for that specific method. For a hand-coded partial (`Build{Rule}HandCoded`), the grammar context is the same rule — the generated sibling delegates to the hand-coded method because the generator can't produce the full body automatically.
-### Hypha grounding (if installed)
+### Hypha grounding — REQUIRED for any spec-dependent verdict
+
+**Whenever a verdict turns on what the specification requires, or on metamodel structure, ground it
+with Hypha and cite the clause. Do not issue such a verdict from memory, from a sibling rule, or from
+the shape of the surrounding code.** A confident-but-ungrounded reading is the failure mode this
+review exists to catch — it has produced real defects here, in both directions: a rule "confirmed"
+against a plausible prior that the clause contradicts, and a correct implementation reported as a
+defect because the reviewer inferred the rule rather than reading it.
+
+State the clause you relied on in the finding. If a verdict rests on a defined term (`parameter`,
+`namingFeature`, root `Namespace`, `redefinedFeature`), quote the definition rather than assuming it.
When the Hypha plugin's skills are available in your session, use them instead of reasoning from memory:
diff --git a/CLAUDE.md b/CLAUDE.md
index 19a72ca86..836ccf835 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -6,6 +6,61 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
SysML2.NET is a .NET C# SDK implementing the OMG SysML v2 specification (based on Beta 4 pilot implementation). It provides metaclass DTOs/POCOs, serializers (JSON, XMI, MessagePack), a REST client, a DAL layer, and a Blazor WebAssembly viewer application. Current version: 0.19.0.
+## Project goal: conformance to the SPECIFICATIONS
+
+**The target for EVERY part of this SDK is conformance to the OMG specifications — not parity with
+any reference implementation.** This is the standing intent across the whole solution, not a
+textual-notation concern. Treat it as governing whenever the two conflict.
+
+The specifications in scope, and what each governs here:
+
+| Specification | Governs |
+|---|---|
+| **KerML 1.0** | abstract syntax (metaclasses, properties, multiplicities, ordering, redefinitions), derived properties and operations (OCL), invariants, model-level semantics including implied relationships (§8.4.2) |
+| **SysML v2.0** | the systems layer on top of KerML — same three concerns — plus the textual concrete syntax |
+| **Systems Modeling API & Services 1.0** | the PIM types and services, and their REST/HTTP binding (`SysML2.NET.REST`, `PIM/`, `SysML2.NET.Serializer.Dictionary`) |
+| **Model interchange** | the project/archive interchange format (`SysML2.NET.Kpar`) |
+
+This applies per layer, with the same standard everywhere:
+
+- **Abstract syntax** (`Core/AutoGen*`) — structure comes from `Resources/*_only_xmi.uml`, the single
+ source of truth. Multiplicity, `ordered`, redefinition/subsetting are contractual, not incidental.
+- **Derived properties and operations** (`Extend/`) — the OCL in the XMI is the contract. Translate
+ it faithfully AND to its intent; where the OCL is terse or leans on a defined term, ground the
+ intent (`hypha:spec-citation`) rather than guessing from a sibling.
+- **Constraints/invariants** — a validation rule is conformant only if it implements the stated
+ invariant, not an approximation that happens to pass the corpus.
+- **Concrete syntax** — the KEBNF and the clauses it comes from; deviations recorded, never silently
+ encoded.
+- **Semantics** — implied relationships and library specializations follow the clause, not what makes
+ a particular model render nicely.
+- **API/PIM** — service behaviour and payload shape follow the API specification, not the behaviour
+ of a particular server.
+
+Rules that hold at every layer:
+
+- **The specification decides.** Where a reference implementation and the specification disagree, the
+ specification wins and the divergence is recorded (`GRAMMAR.md` "Known KEBNF divergences", the
+ deviation ledger, or `GrammarErrata.cs`). Never implement a behaviour whose only justification is
+ "the reference implementation does it".
+- **A reference implementation or its corpus is a test oracle, not the definition of correct.** The
+ pilot's `.sysml` files, a reference API server's responses, a sample model — all catch regressions
+ cheaply, and agreement is usually evidence of correctness. A diff is a question, not a verdict.
+ Note specifically that the pilot's serializer replays the author's original source text wherever
+ the abstract syntax records no choice (optional keywords, name spellings, formatting), so on those
+ points it is not an authority at all — which is what the ledger's ACCEPT categories encode.
+- **Where the spec is genuinely ambiguous**, pick a reading, cite the clause, and say in one line
+ that the clause admits more than one — do not silently encode one reading as fact.
+- **Where the spec offers several valid forms and ranks none**, the choice is ours: make it a setting
+ (see issue #359) or state it as a house convention. Do not present it as a requirement.
+
+**Not yet achieved anywhere — do not claim conformance for any layer.** Confidence today rests
+largely on end-to-end comparison against sample data, which measures non-regression rather than
+conformance. What each layer needs before conformance can be asserted: **round-trip tests** (write →
+re-read → assert the same model; this exists for no serializer today, including the textual writer)
+and **per-invariant unit tests** so an individual rule — a derivation, a constraint, a resolution
+rule, a service contract — is falsifiable in isolation rather than only via an end-to-end diff.
+
## Build & Test Commands
```bash
@@ -224,6 +279,57 @@ Direct pushes to `development` or `master` are forbidden. All work lives on a fe
**Why this split**: the user is the reviewer of record. The commit is the review and the push is the delivery — both are the user's calls. The agent's git involvement is bounded to: (a) create the branch locally + push the empty ref (so the user's push later is frictionless), and (b) leave the rest alone. This was tightened after two failures: first the agent auto-pushed branches to `development` directly, then over-corrected by auto-committing on the user's behalf.
+## Comments: write as few as possible
+
+Comments break readability. Default to **none**; every comment kept needs a justification. This
+applies to production code, tests, and the generator alike.
+
+- **Delete rather than write.** Prefer a better name or an extracted method over an explanation.
+- **Never narrate the what** — a comment paraphrasing the line below it is noise.
+- **No history, no benchmarks, no "this used to…"/"previously"/"an earlier approach"/"was reverted".**
+ Source control holds it. Never put timing measurements in code.
+- **No worked examples, no specific case names.** Never justify a rule with a particular model, a
+ validation-corpus file (`13a-Model Containment`, `ISQ::mass`, `first start;`), or an issue number.
+ State the rule; if it needs authority, cite the clause (`KerML §8.2.3.5.3`) and nothing more.
+- **Never reference the OMG pilot implementation** — or any other tool — as the reason for behaviour.
+ The writer implements the **specification**, not another implementation's choices. Where the spec
+ is genuinely ambiguous, say so in one sentence with the clause; where the KEBNF is defective, that
+ belongs in `GrammarErrata.cs` with its rationale, not scattered through the writers.
+- **No notes to future editors** ("keep in step with X", "do not remove"). Encode it in a guard or a
+ test.
+- **XML docs still required on every type and member** (`DEVELOPMENT_STANDARDS.md` §5.1), but held to
+ one sentence per tag, two as the ceiling — no `` elaborations, no essays.
+
+### The bright line — the ONLY test for keeping an inline comment
+
+Earlier wording said to keep "a non-obvious *why*". That is not testable and gets self-served. The
+rule is:
+
+> **A comment may ONLY state a constraint that would cause a reader to break something if they did
+> not know it. A comment may NEVER explain why the change was made.**
+
+Apply it as a question with a yes/no answer: *if a reader deleted or rewrote this code without the
+comment, would they introduce a defect?* No → delete the comment. "It helps the reviewer understand
+my change" is not a yes; that belongs in the commit message.
+
+**Budget: at most 2 added comment lines per change.** Over that, delete until it fits or ask first.
+
+**Signature words that mean you are writing a commit message, not a comment.** If an added comment
+line contains any of `now`, `previously`, `rather than`, `instead of`, `used to`, `was `, `no
+longer`, `we `, `I `, or restates a `` already on the same member — delete it. A
+`PreToolUse` hook rejects these on `Edit`/`Write`, so it fails loudly rather than reaching review.
+
+Worked example of the failure, from this repo:
+
+```csharp
+// The supplier now records each resource's root as it is read rather than re-deriving it, so
+// interior elements no longer reach this list and no metaclass filter is needed to keep them out.
+var otherRootNamespaces = globalNamespaces?.Where(c => c != null && !ReferenceEquals(c, rootNamespace))
+```
+
+Two banned signatures (`now`, `rather than`), it annotates a self-evident `Where`, and deleting it
+costs a reader nothing. The commit message was the right home for all of it.
+
## Quality rules
- **OCL index base is 1-based; translate positional access accordingly and NEVER mix the two forms.** OCL collections are 1-based (`->at(1)` is the first element; `->first()` ≡ `->at(1)`). Two correct C# forms, applied by target:
diff --git a/SysML2.NET.CodeGenerator/Extensions/GrammarErrata.cs b/SysML2.NET.CodeGenerator/Extensions/GrammarErrata.cs
index c03ed1bce..79eecc5f2 100644
--- a/SysML2.NET.CodeGenerator/Extensions/GrammarErrata.cs
+++ b/SysML2.NET.CodeGenerator/Extensions/GrammarErrata.cs
@@ -90,6 +90,22 @@ public static class GrammarErrata
"parameter path emits 'out verdict', which re-parses as a plain FeatureMembership with " +
"direction out and so loses the metaclass. CalculationBodyItem is already declared in the " +
"same file, so the replacement resolves without any further correction."),
+ new("OccurrenceUsagePrefix",
+ "OccurrenceUsagePrefix : OccurrenceUsage =\n BasicUsagePrefix",
+ "OccurrenceUsagePrefix : OccurrenceUsage =\n UnextendedUsagePrefix",
+ "SysML 8.2.2.9.2 builds OccurrenceUsagePrefix on 'BasicUsagePrefix', which contains no " +
+ "'EndUsagePrefix', so 'isEnd' is UNREACHABLE for every occurrence usage — 'end port p1: P;' " +
+ "and 'end item a;' cannot be written at all. This is issue #124 item 7, already recorded as " +
+ "an accepted divergence in SysML2.NET.CodeGenerator/GRAMMAR.md. Three sources say the " +
+ "notation is real: (1) the pilot's own grammar reaches the end prefix here (org.omg.sysml.xtext " +
+ "SysML.xtext, OccurrenceUsagePrefix); (2) the corpus writes it — 'end port p1: P;' in the " +
+ "Simple Tests ConjugationTest and 'end [1] item a : A' in ConnectionTest; (3) the metamodel " +
+ "sets isEnd on exactly those elements. There is no admissible alternative spelling: " +
+ "'BasicUsagePrefix' reaches 'RefPrefix', whose 'isConstant ?= constant' then renders the " +
+ "pilot's transform-set isConstant as 'constant port p1: P' — which drops isEnd AND asserts a " +
+ "constant the source never wrote. 'UnextendedUsagePrefix = EndUsagePrefix | BasicUsagePrefix' " +
+ "is declared in the same file and dispatches on isEnd, so a non-end usage keeps the exact " +
+ "BasicUsagePrefix behaviour and only an end usage changes."),
new("DefinitionElement",
" | InterfaceDefinition\n | PortDefinition",
" | InterfaceDefinition\n | AllocationDefinition\n | PortDefinition",
diff --git a/SysML2.NET.CodeGenerator/GRAMMAR.md b/SysML2.NET.CodeGenerator/GRAMMAR.md
index 0cad5b8fd..c57d02443 100644
--- a/SysML2.NET.CodeGenerator/GRAMMAR.md
+++ b/SysML2.NET.CodeGenerator/GRAMMAR.md
@@ -231,10 +231,40 @@ re-diagnosed:
| #3 | `MetadataUsage` not wired into any dispatch point | 14-Language Extensions |
| #9 | `SatisfyRequirementUsage` requires `assert` | 08-Requirements |
| #10 | `CaseBodyItem` admits no `ReturnParameterMember` | 10-Analysis and Trades |
-| #11 | `EnumeratedValue` cannot carry prefix metadata (`#Security enum secret`) | 13-Model Containment, 14-Language Extensions |
+| #11 | `EnumeratedValue` cannot carry prefix metadata (`#Security enum secret`) | **CONFIRMED** — see below | 13-Model Containment, 14-Language Extensions, `Simple Tests/MetadataTest` |
Items #2, #4, #5, #6 concern productions with no corpus coverage.
+### #124 item 11 — confirmed, and it silently DROPS a FeatureTyping
+
+```
+EnumerationUsageMember : VariantMembership = MemberPrefix ownedRelatedElement += EnumeratedValue
+EnumeratedValue : EnumerationUsage = 'enum'? Usage ← no prefix slot
+EnumerationUsage : EnumerationUsage = UsagePrefix 'enum' Usage
+```
+
+`EnumeratedValue` has no `UsagePrefix`, so — unlike `EnumerationUsage` — nothing consumes a
+`PrefixMetadataMember` from the cursor. The consequence is worse than an unwritable keyword: the
+annotation is the FIRST entry in the value's `ownedRelationship`, so the cursor is still parked on it
+when `BuildUsage` reaches the positional `FeatureSpecializationPart` guard
+(`cursor.Current is IFeatureTyping || …`). That guard fails and **the typing is never emitted**.
+
+`Simple Tests/MetadataTest` shows it exactly — all three values carry a `FeatureTyping` to
+`ClassificationLevel`, but only the annotated one loses it:
+
+```
+enum uncl: ClassificationLevel = 0; ← [FeatureTyping, FeatureValue]
+enum conf: ClassificationLevel = 1; ← [FeatureTyping, FeatureValue]
+enum secret = 2 { @ Security; } ← [OwningMembership(MetadataUsage), FeatureTyping, FeatureValue]
+```
+
+**Fix when this is taken up:** emit the annotation as a prefix and advance the cursor past it before
+delegating to `Usage`, which yields the pilot's `#Security enum secret : ClassificationLevel = 2;`.
+That is the deviation this item already licenses, and it restores the typing as a side effect.
+`BuildEnumeratedValue` is generated, so the change belongs in the generator — as a HandCoded fallback
+for this rule, the way `EntryTransitionMember` (item 8) is handled. `MetadataTest` stays out of
+validation until then.
+
## Model ↔ notation reconciliations (NOT divergences)
Cases where the grammar offers two conformant productions for one model, so the writer must choose.
@@ -305,3 +335,40 @@ Each condition alone is refuted by one of the three; the conjunction fits all of
`IsValidForDefaultReferenceUsage` still encodes the one spec-mandated case (`!IsEnd &&
Direction.HasValue`): a directed usage is always referential, so the keyword is redundant there.
+
+### `DefaultInterfaceEnd` vs `PortUsage` — the optional `port` keyword on an interface end
+
+```
+InterfaceOccurrenceUsageElement : Usage = DefaultInterfaceEnd | StructureUsageElement | BehaviorUsageElement
+DefaultInterfaceEnd : PortUsage = isEnd ?= 'end' Usage ← end p1: P;
+PortUsage = OccurrenceUsagePrefix 'port' Usage ← end port p1: P;
+```
+
+Both alternatives of `InterfaceOccurrenceUsageElement` reach `PortUsage`, so `end p1: P;` and
+`end port p1: P;` round-trip to the same metaclass and nothing records which the author wrote.
+
+The spec settles the OPTIONALITY and one hard condition on it. SysML 2.0 §7.14.2 Interface
+Definitions and Usages (p. 109, normative): "All the end features of an interface definition or
+usage must be port usages, so the use of the `port` keyword is optional on such end features if no
+owned cross feature is declared on the end."
+
+`DefaultInterfaceEnd` has no notation for a cross feature — only `EndUsagePrefix` carries the
+`( ownedRelationship += OwnedCrossFeatureMember )?` slot — so an end that owns one MUST take the
+`port` form. `IsValidForDefaultInterfaceEnd` implements exactly that condition
+(`IsEnd && OwnedCrossFeature() == null`); where both forms are open the writer takes the
+keyword-less one, the production's first alternative. That choice is ours, not a requirement.
+
+### `BinaryConnectorPart` vs `NaryConnectorPart` — a two-end connector
+
+```
+ConnectorPart : ConnectionUsage = BinaryConnectorPart | NaryConnectorPart
+BinaryConnectorPart = ownedRelationship += ConnectorEndMember 'to' ownedRelationship += ConnectorEndMember
+NaryConnectorPart = '(' ownedRelationship += ConnectorEndMember ',' ownedRelationship += ConnectorEndMember
+ ( ',' ownedRelationship += ConnectorEndMember )* ')'
+```
+
+`NaryConnectorPart` admits exactly two ends, so for a two-end connector `connect a to b` and
+`connect (a, b)` are both conformant and produce the same model. Three or more ends leave only the
+n-ary form. The writer prefers the binary form at exactly two ends — a house convention, not a
+requirement. `IsValidForBinaryConnectorPart`, `IsValidForBinaryConnectorDeclaration` and
+`IsValidForBinaryInterfacePart` each count `EndFeatureMembership` children for it.
diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleGenerationContext.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleGenerationContext.cs
index 9518e796e..c8e5aaee7 100644
--- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleGenerationContext.cs
+++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleGenerationContext.cs
@@ -125,6 +125,15 @@ public TextualNotationRule FindRule(string ruleName)
///
public int LoopProgressCheckCounter { get; set; }
+ ///
+ /// Monotonically-incrementing counter used to produce unique role-based-consumption variable
+ /// names (e.g. elementAsOwningMembership0) across the emission of a single rule body.
+ /// Required because a TryTake out-variable shares the enclosing scope, so two role-based
+ /// consumptions of the same target type in one generated method would collide (CS0136).
+ /// Incremented by RuleProcessor.TryEmitPinnedRuleConsumption.
+ ///
+ public int TakenElementCounter { get; set; }
+
///
/// Determines whether the next sibling element is a terminal that uses AppendLine
/// (e.g., {, }, ;), in which case a trailing space would be unnecessary.
diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs
index 8c3ae3400..1c610d034 100644
--- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs
+++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs
@@ -1,4 +1,4 @@
-// -------------------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------------------
//
//
// Copyright 2022-2026 Starion Group S.A.
@@ -76,10 +76,17 @@ private void EmitCollectionNonTerminalLoop(EncodedTextWriter writer, IClass umlC
var whileTypeExclusion = this.ResolveCollectionWhileTypeCondition(cursorVariableName, umlClass, referencedRule, propertyName, ruleGenerationContext);
+ // A dispatcher loop must test the types it can consume; a wider condition stalls it.
+ var dispatcherTypeCondition = TryResolveDispatcherConsumedTypeCondition(cursorVariableName, referencedRule, propertyName, umlClass, ruleGenerationContext);
+
string whileCondition;
var whileConditionIsBareNullTest = false;
- if (!string.IsNullOrWhiteSpace(whileTypeExclusion))
+ if (dispatcherTypeCondition != null)
+ {
+ whileCondition = dispatcherTypeCondition;
+ }
+ else if (!string.IsNullOrWhiteSpace(whileTypeExclusion))
{
whileCondition = whileTypeExclusion;
}
@@ -136,14 +143,8 @@ private void EmitCollectionNonTerminalLoop(EncodedTextWriter writer, IClass umlC
}
}
- // A guarded body-item rule (IsGuardedBodyItemRule) admits elements that have no
- // notation, and its per-item builder refuses to consume them WITHOUT advancing the
- // cursor. The loop must therefore test the same predicate as the enclosing entry
- // guard: a bare non-null test spins forever on the first refused element. The
- // guarded form only replaces the BARE null-test fallback: when a type-derived
- // while-condition already bounds the loop (next-type exclusion or content-type
- // guard, e.g. CalculationBodyPart's `is not IResultExpressionMembership`), that
- // stronger structural bound stays.
+ // A guarded body-item rule refuses elements without advancing, so the loop must test the same
+ // predicate as the entry guard.
if (whileConditionIsBareNullTest && IsGuardedBodyItemRule(nonTerminalElement.Name))
{
var guardVariableName = $"{targetProperty.Name.LowerCaseFirstLetter()}BodyItem";
@@ -193,7 +194,7 @@ private void EmitCollectionNonTerminalLoop(EncodedTextWriter writer, IClass umlC
/// The group's bare (non-assignment) non-terminal alternatives
/// The cursor driving the repeated group
/// The current
- private static void EmitCollectionGroupFallThrough(EncodedTextWriter writer, IClass umlClass, List dispatcherNonTerminals, string cursorVariableName, RuleGenerationContext ruleGenerationContext)
+ private static bool EmitCollectionGroupFallThrough(EncodedTextWriter writer, IClass umlClass, List dispatcherNonTerminals, string cursorVariableName, RuleGenerationContext ruleGenerationContext)
{
var dispatcherCalls = dispatcherNonTerminals
.Select(nonTerminal =>
@@ -209,20 +210,161 @@ private static void EmitCollectionGroupFallThrough(EncodedTextWriter writer, ICl
if (dispatcherCalls.Count == 0)
{
- writer.WriteSafeString($"{cursorVariableName}.Move();{Environment.NewLine}");
+ var ruleName = ruleGenerationContext.NamedElementToGenerate?.Name ?? "Unknown";
+ writer.WriteSafeString($"throw new System.InvalidOperationException($\"The textual notation writer cannot place the current element ({{{cursorVariableName}.Current?.GetType().Name}}) while building '{ruleName}' — no alternative of the rule claims it, so it would be silently dropped.\");{Environment.NewLine}");
- return;
+ return true;
}
foreach (var dispatcherCall in dispatcherCalls)
{
writer.WriteSafeString($"{dispatcherCall}{Environment.NewLine}");
}
+
+ return false;
}
///
/// Resolves the type condition for a collection while loop.
///
+ ///
+ /// Builds a POSITIVE while condition for a * loop whose body is a pure dispatcher
+ /// rule (every alternative a bare non-terminal), testing exactly the element types that dispatcher
+ /// consumes from the loop's cursor — e.g. FeatureSpecialization consumes the
+ /// Specialization kinds, so a FeatureMembership must not enter the loop.
+ ///
+ /// The cursor the loop reads.
+ /// The rule invoked per iteration.
+ /// The collection property the loop consumes from.
+ /// The class hosting the current rule (provides the UML cache).
+ /// The current .
+ /// The condition, or when the rule is not a resolvable dispatcher.
+ ///
+ /// Declines when the consumed set cannot be fully resolved, or when a following sibling consumes a
+ /// type the set also admits — the existing next-type exclusion is the correct guard there, and a
+ /// positive test would let the loop swallow the sibling's element.
+ ///
+ private static string TryResolveDispatcherConsumedTypeCondition(string cursorVariableName, TextualNotationRule referencedRule, string propertyName, IClass umlClass, RuleGenerationContext ruleGenerationContext)
+ {
+ var isPureDispatcher = referencedRule.Alternatives.Count > 1
+ && referencedRule.Alternatives.All(alternative => alternative.Elements.Count == 1 && alternative.Elements[0] is NonTerminalElement);
+
+ if (!isPureDispatcher)
+ {
+ return null;
+ }
+
+ var consumedClasses = new List();
+
+ foreach (var alternative in referencedRule.Alternatives)
+ {
+ var alternativeClasses = CollectCursorConsumedClasses((NonTerminalElement)alternative.Elements[0], propertyName, umlClass, ruleGenerationContext, new HashSet(StringComparer.Ordinal));
+
+ // An incomplete set would silently skip the elements it failed to account for.
+ if (alternativeClasses.Count == 0)
+ {
+ return null;
+ }
+
+ consumedClasses.AddRange(alternativeClasses.Where(consumedClass => !consumedClasses.Contains(consumedClass)));
+ }
+
+ var siblings = ruleGenerationContext.CurrentSiblingElements;
+ var nextIndex = ruleGenerationContext.CurrentElementIndex + 1;
+
+ if (siblings != null && nextIndex < siblings.Count && siblings[nextIndex] is AssignmentElement { Operator: "+=" } nextAssignment)
+ {
+ var nextTypeName = ResolveAssignmentTargetTypeName(nextAssignment, umlClass, ruleGenerationContext);
+
+ if (nextTypeName != null && consumedClasses.Any(consumedClass => nextTypeName.StartsWith(consumedClass.QueryFullyQualifiedTypeName(), StringComparison.Ordinal)))
+ {
+ return null;
+ }
+ }
+
+ var typeNames = consumedClasses.Select(consumedClass => consumedClass.QueryFullyQualifiedTypeName()).ToList();
+
+ return typeNames.Count == 1
+ ? $"{cursorVariableName}.Current is {typeNames[0]}"
+ : $"{cursorVariableName}.Current is ({string.Join(" or ", typeNames)})";
+ }
+
+ ///
+ /// Recursively collects the element classes a rule consumes from
+ /// via +=, descending through bare non-terminal references.
+ ///
+ /// The rule reference to walk.
+ /// The collection property whose consumption is collected.
+ /// The class hosting the current rule (provides the UML cache).
+ /// The current .
+ /// Rule names already walked, preventing infinite recursion.
+ /// The distinct consumed classes; empty when none resolve.
+ private static List CollectCursorConsumedClasses(NonTerminalElement nonTerminalElement, string propertyName, IClass umlClass, RuleGenerationContext ruleGenerationContext, HashSet visitedRules)
+ {
+ var consumedClasses = new List();
+
+ if (!visitedRules.Add(nonTerminalElement.Name))
+ {
+ return consumedClasses;
+ }
+
+ var rule = ruleGenerationContext.FindRule(nonTerminalElement.Name);
+
+ if (rule == null)
+ {
+ return consumedClasses;
+ }
+
+ foreach (var element in rule.Alternatives.SelectMany(alternative => FlattenRuleElements(alternative.Elements)))
+ {
+ switch (element)
+ {
+ case AssignmentElement { Operator: "+=", Value: NonTerminalElement valueNonTerminal } assignment
+ when string.Equals(assignment.Property, propertyName, StringComparison.OrdinalIgnoreCase):
+ {
+ var itemRule = ruleGenerationContext.FindRule(valueNonTerminal.Name);
+ var itemClass = RuleQueryUtilities.FindClass(umlClass.Cache, itemRule?.EffectiveTarget ?? valueNonTerminal.Name);
+
+ if (itemClass != null && !consumedClasses.Contains(itemClass))
+ {
+ consumedClasses.Add(itemClass);
+ }
+
+ break;
+ }
+
+ case NonTerminalElement nestedNonTerminal:
+ consumedClasses.AddRange(CollectCursorConsumedClasses(nestedNonTerminal, propertyName, umlClass, ruleGenerationContext, visitedRules)
+ .Where(nestedClass => !consumedClasses.Contains(nestedClass)));
+ break;
+ }
+ }
+
+ return consumedClasses;
+ }
+
+ ///
+ /// Flattens a rule-element sequence, expanding group alternatives so nested assignments and
+ /// references are visited.
+ ///
+ /// The elements to flatten.
+ /// The flattened element sequence.
+ private static IEnumerable FlattenRuleElements(IEnumerable elements)
+ {
+ foreach (var element in elements)
+ {
+ yield return element;
+
+ if (element is GroupElement groupElement)
+ {
+ foreach (var nested in FlattenRuleElements(groupElement.Alternatives.SelectMany(alternative => alternative.Elements)))
+ {
+ yield return nested;
+ }
+ }
+ }
+ }
+
private string ResolveCollectionWhileTypeCondition(string cursorVariableName, IClass umlClass, TextualNotationRule collectionRule, string outerPropertyName, RuleGenerationContext ruleGenerationContext)
{
var siblings = ruleGenerationContext.CurrentSiblingElements;
@@ -251,12 +393,7 @@ private string ResolveCollectionWhileTypeCondition(string cursorVariableName, IC
var itemRule = ruleGenerationContext.FindRule(assignmentNonTerminals[0].Name);
var itemTypeTarget = itemRule != null ? itemRule.EffectiveTarget : null;
- // The item rule's own target is the WRAPPER type for a thin owning wrapper
- // (X : OwningMembership = … ownedRelatedElement = Y), which every sibling wrapper on this
- // cursor also satisfies — `individual def` consumed its own EmptyMultiplicityMember as a
- // DefinitionExtensionKeyword and emitted a stray '#'. Prefer the wrapped-type guard when
- // one is available; otherwise keep the coarse test rather than falling through to a
- // weaker condition that could admit elements the loop body will not consume.
+ // A thin owning wrapper's own target is satisfied by every sibling wrapper on this cursor.
var wrappedTypeGuard = this.ResolveContentTypeGuard(cursorVariableName, collectionRule, outerPropertyName, umlClass, ruleGenerationContext);
if (!string.IsNullOrWhiteSpace(wrappedTypeGuard))
@@ -333,9 +470,7 @@ 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.
+ // An absence constraint on the referenced element's contents is not derivable from body shape.
if (RequiresHandCodedContentGuard(referencedRule.RuleName))
{
var handCodedGuardVariableName = $"{referencedRule.RuleName.LowerCaseFirstLetter()}Guard{ruleGenerationContext.NarrowedTypeCheckCounter}";
@@ -533,8 +668,6 @@ private static string TryBuildCursorTypedCheck(EncodedTextWriter writer, Textual
return null;
}
- // 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)
diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs
index f1e3358e6..7ca847381 100644
--- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs
+++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.ElementProcessing.cs
@@ -106,8 +106,7 @@ internal void ProcessRuleElement(EncodedTextWriter writer, IClass umlClass, Rule
}
}
- // 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..`).
+ // A `( X )+ X` shape must leave one element for the mandatory tail.
var reservationGuard = ResolveTrailingConsumptionReservation(cursorToUse, umlClass, ruleGenerationContext);
if (groupTypeGuard.StartsWith("__FULL_GUARD__"))
@@ -145,12 +144,8 @@ internal void ProcessRuleElement(EncodedTextWriter writer, IClass umlClass, Rule
.OfType()
.ToList();
- // Alternatives of the repeated group that are a BARE non-terminal rather than a
- // `+=` assignment (e.g. `TypeBodyElement` in
- // `( TypeBodyElement | ownedRelationship += ReturnFeatureMember )*`). Such a rule is a
- // per-item dispatcher over the SAME cursor that advances the cursor itself, so it
- // becomes the loop's fall-through arm — without it the group's switch has no case for
- // those elements and the trailing `Move()` silently discards them.
+ // A bare non-terminal alternative is a per-item dispatcher that advances the cursor itself, so
+ // it becomes the loop's fall-through arm.
var groupDispatcherNonTerminals = groupElement.Alternatives
.SelectMany(alternative => alternative.Elements)
.OfType()
@@ -187,10 +182,7 @@ internal void ProcessRuleElement(EncodedTextWriter writer, IClass umlClass, Rule
var groupOrderedElements = RuleQueryUtilities.OrderElementsByInheritance(groupNonTerminals, umlClass.Cache, ruleGenerationContext);
- // A `*` group must not swallow an element that a FOLLOWING sibling consumes
- // from the same cursor — `( … )* ( ownedRelationship += ResultExpressionMember )?`
- // left the trailing optional facing an exhausted cursor and dropped the result
- // expression. Exclude the next sibling's target type from the loop condition.
+ // Exclude the next sibling's target type, or the group swallows the element it consumes.
var groupWhileCondition = this.ResolveCollectionWhileTypeCondition(groupCursorVarName, umlClass, null, groupPropertyName, ruleGenerationContext);
if (string.IsNullOrWhiteSpace(groupWhileCondition))
@@ -225,8 +217,10 @@ internal void ProcessRuleElement(EncodedTextWriter writer, IClass umlClass, Rule
}
writer.WriteSafeString($"default:{Environment.NewLine}");
- EmitCollectionGroupFallThrough(writer, umlClass, groupDispatcherNonTerminals, groupCursorVarName, ruleGenerationContext);
- writer.WriteSafeString($"break;{Environment.NewLine}");
+ if (!EmitCollectionGroupFallThrough(writer, umlClass, groupDispatcherNonTerminals, groupCursorVarName, ruleGenerationContext))
+ {
+ writer.WriteSafeString($"break;{Environment.NewLine}");
+ }
writer.WriteSafeString($"}}{Environment.NewLine}");
EmitLoopProgressAssertion(writer, groupCursorVarName, groupPositionVariableName, groupElement.TextualNotationRule?.RuleName ?? groupPropertyName);
@@ -327,24 +321,9 @@ private static string ResolveTrailingConsumptionReservation(CursorDefinition cur
/// The class hosting the current rule (provides the UML cache).
/// The additional guard clause, or an empty string when the repetition cannot match one.
///
- /// The grammar never repeats a result member: it always gives one its OWN slot in the enclosing rule
- /// (EmptyResultMember, ConstructorResultMember, ReturnParameterMember), never a
- /// comma-separated repetition. A repetition that shares the enclosing rule's cursor will therefore
- /// swallow it whenever the repetition's guard type happens to be one of its supertypes:
- ///
- /// InvocationExpression = ownedRelationship += InstantiatedTypeMember
- /// ArgumentList
- /// ownedRelationship += EmptyResultMember
- /// PositionalArgumentList = ownedRelationship += ArgumentMember
- /// ( ',' ownedRelationship += ArgumentMember )*
- ///
- /// ArgumentMember : ParameterMembership and EmptyResultMember : ReturnParameterMembership
- /// — a ParameterMembership — so the loop emits a separator for it and then renders nothing:
- /// f(a, ).
- /// Gating on subtype overlap keeps this general without touching repetitions it cannot affect.
- /// Of the fifteen comma-repetition shapes across both grammars only two — ArgumentMember and
- /// NamedArgumentMember — guard on a supertype of ReturnParameterMembership; the rest test
- /// relationship types or sibling membership subtypes, for which the clause would be dead code.
+ /// The grammar never repeats a result member — it always gives one its own slot in the enclosing rule
+ /// — so a repetition sharing that cursor swallows it whenever the repetition's guard type is one of
+ /// its supertypes. Gated on subtype overlap, which leaves repetitions it cannot affect untouched.
///
private static string ResolveResultMemberExclusion(CursorDefinition cursorDefinition, IClass itemTargetClass, IClass umlClass)
{
@@ -383,6 +362,18 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass
if (assignmentElement.Value is NonTerminalElement nonTerminalElement)
{
var cursorToUse = ruleGenerationContext.DefinedCursors.Single(x => x.ApplicableRuleElements.Contains(assignmentElement));
+
+ // PendingCursorMove lands the Move() inside the type-discrimination block, so the cursor
+ // advances only on real `+=` consumption.
+ var shouldEmitCursorMove = !isPartOfMultipleAlternative
+ && assignmentElement.Container is not GroupElement { IsCollection: true };
+
+ if (shouldEmitCursorMove
+ && TryEmitPinnedRuleConsumption(writer, umlClass, nonTerminalElement, cursorToUse, ruleGenerationContext))
+ {
+ return;
+ }
+
var usedVariable = $"{cursorToUse.CursorVariableName}.Current";
var previousVariableName = ruleGenerationContext.CurrentVariableName;
@@ -390,12 +381,6 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass
var previousCaller = ruleGenerationContext.CallerRule;
ruleGenerationContext.CallerRule = assignmentElement;
- // 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 };
-
if (shouldEmitCursorMove)
{
ruleGenerationContext.PendingCursorMove = $"{Environment.NewLine}{cursorToUse.CursorVariableName}.Move();{Environment.NewLine}";
@@ -465,11 +450,9 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass
}
else if (assignmentElement.Value is NonTerminalElement { Name: "STRING_VALUE" })
{
- // STRING_VALUE carries its own quotes — '"' ( STRING_CHARACTER |
- // ESCAPE_SEQUENCE )* '"' — and the model holds the DECODED string, so the
- // writer owns re-encoding it. See AppendStringValue. Keyed on the TERMINAL
- // rather than the property type: a String-typed test would also quote
- // declaredName and every other string the grammar writes bare.
+ // STRING_VALUE carries its own quotes and the model holds the DECODED string, so the writer owns
+ // re-encoding it. Keyed on the TERMINAL, not the property type, which would also quote every
+ // string the grammar writes bare.
writer.WriteSafeString($"SharedTextualNotationBuilder.AppendStringValue(stringBuilder, poco.{targetPropertyName});");
}
else if (string.Equals(targetPropertyName, "Operator", StringComparison.Ordinal))
@@ -515,13 +498,7 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass
}
else if (targetProperty.QueryIsEnum())
{
- // A result member's own keyword already conveys the direction, so writing it
- // again emits `return out verdict` where the notation is `return verdict`.
- // Testing the owning Membership follows the metamodel's own idiom — Feature's
- // parameter-redefinition constraint selects parameters with `direction <> null`
- // and then rejects those whose owningFeatureMembership is a result membership.
- // Both OMG names come from NotationInvariants, which reports either of them
- // going missing rather than letting this rule switch itself off.
+ // A result member's own keyword already conveys the direction.
var impliedDirectionProperty = NotationInvariants.QueryMetamodelName(NotationInvariants.ImpliedDirectionProperty);
var isImpliedDirectionProperty = string.Equals(targetProperty.Name, impliedDirectionProperty, StringComparison.Ordinal);
@@ -639,13 +616,65 @@ internal void ProcessAssignmentElement(EncodedTextWriter writer, IClass umlClass
}
else
{
- // The grammar's property does not resolve against the metamodel class (e.g. the kebnf's
- // `ownedFeatureMember` typo) — delegate to the HandCoded sibling.
+ // The grammar's property does not resolve against the metamodel class.
var handCodedRuleName = assignmentElement.TextualNotationRule?.RuleName ?? "Unknown";
EmitHandCodedFallback(writer, handCodedRuleName, ruleGenerationContext);
}
}
+ ///
+ /// Emits the role-based consumption for a += assignment whose referenced rule carries a
+ /// role discriminator (a pinned enum constant like { kind = 'guard' }, or a structural
+ /// signature) — TryTake with the rule's predicate — locating the element by ROLE instead
+ /// of by cursor position. The matching guard condition is emitted by
+ /// with the IDENTICAL predicate, so condition
+ /// and consumption agree on the element they select.
+ ///
+ /// The used to write output.
+ /// The class hosting the current rule (provides the UML cache).
+ /// The referenced rule's .
+ /// The cursor the assignment consumes from.
+ /// The current .
+ /// true when the consumption was emitted; false when the rule is not discriminating and the positional path must run.
+ private static bool TryEmitPinnedRuleConsumption(EncodedTextWriter writer, IClass umlClass, NonTerminalElement nonTerminalElement, CursorDefinition cursorDefinition, RuleGenerationContext ruleGenerationContext)
+ {
+ var referencedRule = ruleGenerationContext.FindRule(nonTerminalElement.Name);
+ var typeTarget = referencedRule?.EffectiveTarget;
+
+ if (typeTarget == null)
+ {
+ return false;
+ }
+
+ var targetClass = RuleQueryUtilities.FindClass(umlClass.Cache, typeTarget);
+ var targetTypeName = targetClass?.QueryFullyQualifiedTypeName();
+
+ if (targetTypeName == null)
+ {
+ return false;
+ }
+
+ var predicate = ResolveRoleBasedPredicate(referencedRule, targetClass, ruleGenerationContext);
+
+ if (predicate == null)
+ {
+ return false;
+ }
+
+ var takenVariableName = $"elementAs{targetClass.Name}{ruleGenerationContext.TakenElementCounter++}";
+
+ var builderCall = string.Equals(typeTarget, ruleGenerationContext.NamedElementToGenerate.Name, StringComparison.Ordinal)
+ ? $"Build{nonTerminalElement.Name}({takenVariableName}, writerContext, stringBuilder);"
+ : $"{typeTarget}TextualNotationBuilder.Build{nonTerminalElement.Name}({takenVariableName}, writerContext, stringBuilder);";
+
+ writer.WriteSafeString($"{Environment.NewLine}if ({cursorDefinition.CursorVariableName}.TryTake<{targetTypeName}>({predicate}, out var {takenVariableName})){Environment.NewLine}");
+ writer.WriteSafeString($"{{{Environment.NewLine}");
+ writer.WriteSafeString(builderCall);
+ writer.WriteSafeString($"{Environment.NewLine}}}");
+
+ return true;
+ }
+
///
/// Process a
///
diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs
index 597c3f448..2e327eb12 100644
--- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs
+++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs
@@ -1,4 +1,4 @@
-// -------------------------------------------------------------------------------------------------
+// -------------------------------------------------------------------------------------------------
//
//
// Copyright 2022-2026 Starion Group S.A.
@@ -230,9 +230,8 @@ private bool TryHandleEmptyVsNonEmptyMembership(EncodedTextWriter writer, IClass
var typeName = emptyTarget.QueryFullyQualifiedTypeName();
var cursorVarName = cursor.CursorVariableName;
- // 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.
+ // An "Empty" wrapper rule usually still assigns its collection, so a Count-based discriminator
+ // cannot select the empty branch; discriminate on the wrapped element type instead.
var wrappedNonEmptyTypeName = QueryWrappedElementTypeName(nonEmptyBranch.NonTerminal, umlClass, ruleGenerationContext);
var wrappedEmptyTypeName = QueryWrappedElementTypeName(emptyBranch.NonTerminal, umlClass, ruleGenerationContext);
@@ -594,13 +593,16 @@ private void ProcessUnitypedAlternativesWithOneElement(EncodedTextWriter writer,
{
var allProperties = duplicateGroup.Key.QueryAllProperties();
- var elementBoolProps = new List<(NonTerminalElement RuleElement, List BoolProps)>();
+ var elementBoolProps = new List<(NonTerminalElement RuleElement, List BoolProps, List SettableBoolProps)>();
foreach (var ruleElement in duplicateGroup.Value.Select(x => x.RuleElement))
{
var referencedRule = ruleGenerationContext.AllRules.Single(x => x.RuleName == ruleElement.Name);
var booleanProperties = RuleQueryUtilities.QueryBooleanAssignmentProperties(referencedRule, ruleGenerationContext.AllRules);
- elementBoolProps.Add((ruleElement, booleanProperties));
+
+ // A sibling that may set the property optionally can satisfy a guard on it too.
+ var settableBooleanProperties = RuleQueryUtilities.QueryAllBooleanAssignmentProperties(referencedRule, ruleGenerationContext.AllRules);
+ elementBoolProps.Add((ruleElement, booleanProperties, settableBooleanProperties));
}
for (var elementIndex = 0; elementIndex < elementBoolProps.Count; elementIndex++)
@@ -612,7 +614,7 @@ private void ProcessUnitypedAlternativesWithOneElement(EncodedTextWriter writer,
{
if (otherIndex != elementIndex)
{
- foreach (var prop in elementBoolProps[otherIndex].BoolProps)
+ foreach (var prop in elementBoolProps[otherIndex].SettableBoolProps)
{
othersProperties.Add(prop);
}
@@ -695,10 +697,7 @@ private void ProcessUnitypedAlternativesWithOneElement(EncodedTextWriter writer,
}
}
- // 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.
+ // An unguarded fall-through is only safe when no sibling alternative targets a supertype.
foreach (var duplicateGroup in duplicateClasses)
{
var stillUnguarded = duplicateGroup.Value
@@ -740,9 +739,7 @@ private void ProcessUnitypedAlternativesWithOneElement(EncodedTextWriter writer,
}
}
- // Hand-coded alternative guards: an alternative whose discriminator cannot be derived
- // from the rule body at all (it needs cursor lookahead, not a property test) is
- // allowlisted by rule name and delegates to a hand-coded IsValidFor{Rule}.
+ // A discriminator needing cursor lookahead cannot be derived from the rule body.
foreach (var unguarded in mappedNonTerminalElements
.Select(element => element.RuleElement)
.Where(ruleElement => RequiresHandCodedAlternativeGuard(ruleElement.Name)
@@ -751,12 +748,8 @@ private void ProcessUnitypedAlternativesWithOneElement(EncodedTextWriter writer,
whenGuards[unguarded] = $"{{0}}.IsValidFor{unguarded.Name}(writerContext)";
}
- // 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.
+ // A rule using its own target class as an alternative makes that arm the catch-all, so sibling
+ // arms need guards to let an anonymous subclass instance fall through.
var generatingClassForSelfDefault = ruleGenerationContext.NamedElementToGenerate as IClass;
var isSelfDefault = generatingClassForSelfDefault != null
&& mappedNonTerminalElements.Any(element => element.UmlClass == generatingClassForSelfDefault);
@@ -789,6 +782,8 @@ private void ProcessUnitypedAlternativesWithOneElement(EncodedTextWriter writer,
}
}
+ AssertDuplicateGroupGuardsAreDisjoint(duplicateClasses, whenGuards);
+
var reorderedElements = new List<(NonTerminalElement RuleElement, IClass UmlClass)>();
var processedDuplicateClasses = new HashSet();
@@ -812,19 +807,11 @@ private void ProcessUnitypedAlternativesWithOneElement(EncodedTextWriter writer,
var defaultElement = mappedNonTerminalElements
.LastOrDefault(x => x.UmlClass == ruleGenerationContext.NamedElementToGenerate && !whenGuards.ContainsKey(x.RuleElement));
- // 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.
+ // 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 precedes an arm targeting its supertype.
+ // The sort must be STABLE: equal-depth arms are mutually disjoint, so their relative order does
+ // not affect dispatch but does affect the emitted text, and an unstable sort makes regeneration
+ // non-deterministic.
mappedNonTerminalElements =
[
.. mappedNonTerminalElements
@@ -1353,6 +1340,46 @@ private static void CollectReachableTargetClasses(string ruleName, IXmiElementCa
}
}
+ ///
+ /// Fails generation when a duplicate dispatch group's arms cannot be told apart — more than one
+ /// arm without a guard, or two arms carrying the IDENTICAL guard — because C# when clauses
+ /// evaluate in order and the first arm would silently claim every instance the second one owns.
+ /// This is the first tranche of the disjointness safety net: it proves nothing about guards that
+ /// differ textually but overlap semantically, only that no arm is TRIVIALLY unreachable.
+ ///
+ /// The duplicate dispatch groups, keyed by their shared target class.
+ /// The resolved when guard per alternative, after every guard tier ran.
+ /// When a group's arms are not trivially disjoint.
+ private static void AssertDuplicateGroupGuardsAreDisjoint(Dictionary> duplicateClasses, Dictionary whenGuards)
+ {
+ foreach (var duplicateGroup in duplicateClasses)
+ {
+ var unguardedRuleNames = duplicateGroup.Value
+ .Where(element => !whenGuards.ContainsKey(element.RuleElement))
+ .Select(element => element.RuleElement.Name)
+ .ToList();
+
+ if (unguardedRuleNames.Count > 1)
+ {
+ throw new InvalidOperationException(
+ $"Alternatives '{string.Join("', '", unguardedRuleNames)}' all target '{duplicateGroup.Key.Name}' and none of them resolved a distinguishing guard — the first arm would silently claim every instance. Add a discriminator (pinned constant, boolean assignment, or IsValidFor guard) for all but one.");
+ }
+
+ var collidingGuardGroup = duplicateGroup.Value
+ .Where(element => whenGuards.ContainsKey(element.RuleElement))
+ .GroupBy(element => whenGuards[element.RuleElement], StringComparer.Ordinal)
+ .FirstOrDefault(guardGroup => guardGroup.Count() > 1);
+
+ if (collidingGuardGroup != null)
+ {
+ var collidingRuleNames = collidingGuardGroup.Select(element => element.RuleElement.Name);
+
+ throw new InvalidOperationException(
+ $"Alternatives '{string.Join("', '", collidingRuleNames)}' targeting '{duplicateGroup.Key.Name}' resolved the IDENTICAL guard '{collidingGuardGroup.Key}' — the first arm would silently claim every instance. Their discriminators must be disjoint.");
+ }
+ }
+ }
+
///
/// Runs the structural-predicate walk for and returns the raw clause
/// list, seeding the per-walk visited-rules set and cursor-state bookkeeping.
@@ -1395,25 +1422,24 @@ private static List CollectGuardClausesForRule(TextualNotationRule rule,
///
/// 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.
+ /// Identical alternatives collapse. A clauseless alternative constrains nothing, so the whole
+ /// disjunction it belongs to is unconstrained and yields no predicate.
///
/// One list of synthesized clauses per grammar alternative.
- /// A single combined predicate string, or null when every alternative is empty.
+ /// A single combined predicate string, or null when any alternative is unconstrained.
private static string CombineAlternativesAsOr(List> perAlternativeClauses)
{
- var nonEmpty = perAlternativeClauses
- .Where(altClauses => altClauses.Count > 0)
- .Select(altClauses => altClauses.Count == 1
- ? altClauses[0]
- : "(" + string.Join(" && ", altClauses) + ")")
- .ToList();
-
- if (nonEmpty.Count == 0)
+ if (perAlternativeClauses.Count == 0 || perAlternativeClauses.Any(altClauses => altClauses.Count == 0))
{
return null;
}
- var distinct = nonEmpty.Distinct(StringComparer.Ordinal).ToList();
+ var distinct = perAlternativeClauses
+ .Select(altClauses => altClauses.Count == 1
+ ? altClauses[0]
+ : "(" + string.Join(" && ", altClauses) + ")")
+ .Distinct(StringComparer.Ordinal)
+ .ToList();
if (distinct.Count == 1)
{
@@ -1499,8 +1525,6 @@ private static void CollectGuardClauses(IEnumerable elements, IEnum
}
}
- // Depth exhausted, rule not found, or already visited — conservatively
- // suppress later cursor predicates.
cursorMayHaveAdvanced = true;
break;
}
@@ -1547,7 +1571,7 @@ private static void CollectGuardClauses(IEnumerable elements, IEnum
/// - prop = NonTerminal → {0}.{Prop} is I{RHS-target} (narrows when possible, else != null)
/// - ownedRelationship += NonTerminal (first occurrence) → cursor predicate
/// - prop += NonTerminal for any other collection → {0}.{Prop}.OfType<I{RHS-target}>().Any()
- /// - prop ?= 'kw' → null (already handled by the boolean discriminator pass)
+ /// - prop ?= 'kw' → {0}.{Prop} — the keyword is mandatory within its production, so the property is necessarily set
///
///
/// The to translate
@@ -1559,7 +1583,7 @@ private static void CollectGuardClauses(IEnumerable elements, IEnum
/// A template clause, or null when no clause applies.
private static string TryBuildClauseForAssignment(AssignmentElement assignment, IEnumerable targetProperties, IXmiElementCache cache, IReadOnlyList allRules, ref bool firstCursorEmitted, bool cursorMayHaveAdvanced)
{
- if (assignment?.Property == null || assignment.Operator == "?=")
+ if (assignment?.Property == null)
{
return null;
}
@@ -1588,6 +1612,8 @@ private static string TryBuildClauseForAssignment(AssignmentElement assignment,
return BuildScalarAssignmentClause(assignment, matchingProperty, propertyAccessor, cache, allRules);
case "+=":
return BuildCollectionAssignmentClause(assignment, propertyAccessor, cache, allRules, ref firstCursorEmitted, cursorMayHaveAdvanced);
+ case "?=":
+ return $"{{0}}.{propertyAccessor}";
default:
return null;
}
@@ -1635,7 +1661,6 @@ private static string BuildScalarAssignmentClause(AssignmentElement assignment,
if (rhsTargetClass != null)
{
- // `is I{Rhs}` needs a reference-typed property to compile.
return matchingProperty.QueryIsReferenceType()
? $"{{0}}.{propertyAccessor} is {rhsTargetClass.QueryFullyQualifiedTypeName()}"
: null;
diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs
index 76087a158..78b63c68b 100644
--- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs
+++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs
@@ -29,6 +29,7 @@ namespace SysML2.NET.CodeGenerator.HandleBarHelpers
using SysML2.NET.CodeGenerator.Extensions;
using SysML2.NET.CodeGenerator.Grammar.Model;
+ using uml4net.Classification;
using uml4net.Extensions;
using uml4net.SimpleClassifiers;
using uml4net.StructuredClassifiers;
@@ -113,14 +114,9 @@ private void EmitElements(EncodedTextWriter writer, IClass umlClass, List
- /// Text-free members the pilot is KNOWN to store before the elements the production declares ahead of
- /// them, verified against real pilot output.
- /// Deliberately an allowlist, not a structural rule. Emitting no text does NOT imply the model
- /// may store the element anywhere: storage order is a per-rule implementation detail and it goes BOTH
- /// ways. EmptyMultiplicityMember is stored FIRST though declared last, while
- /// EmptyResultMember / ReturnParameterMembership is stored LAST as declared (in the
- /// OperatorExpression family, InvocationExpression and FeatureReferenceExpression).
- /// Hoisting the latter would strand the cursor on it. Only add a name here after checking real output.
+ /// Text-free members stored before the elements the production declares ahead of them.
+ /// An allowlist, not a structural rule: emitting no text does not imply the element may be
+ /// stored anywhere, and storage order goes both ways.
///
private static readonly HashSet HoistableTextFreeMembers = new(StringComparer.Ordinal)
{
@@ -130,11 +126,8 @@ private void EmitElements(EncodedTextWriter writer, IClass umlClass, List
/// Moves a lone += element whose production emits NO text to the front of the alternative.
/// Such an element has no observable position in the notation, so the grammar cannot constrain
- /// where the parser puts it in the collection — and the pilot does not always put it where the
- /// production does. Consuming it first keeps the cursor aligned for the elements that DO emit text;
- /// leaving it in place strands the cursor on it (e.g. IndividualDefinition declares
- /// EmptyMultiplicityMember last but the model stores it first, hiding the Subclassification
- /// that Definition must read).
+ /// where it is stored. Consuming it first keeps the cursor aligned for the elements that do emit
+ /// text; leaving it in place strands the cursor on it.
/// Only a LONE such element is hoisted: when several appear (e.g. TransitionUsage's two
/// EmptyParameterMembers) their relative order decides which pairs with which sibling, so
/// moving them would change meaning.
@@ -152,10 +145,7 @@ private static List HoistSingleNonNotationalConsumption(ListThe property pattern, or when nothing enum-typed is pinned.
private static string ResolvePinnedConstantPattern(TextualNotationRule referencedRule, IClass targetClass)
{
+ return TryResolvePinnedConstant(referencedRule, targetClass, out var propertyName, out var enumTypeName, out var literalName)
+ ? $"{{ {propertyName}: {enumTypeName}.{literalName} }}"
+ : null;
+ }
+
+ ///
+ /// Builds the C# lambda predicate testing a constant a rule pins via a non-parsing assignment
+ /// ({ kind = 'guard' }), for use with the role-based cursor primitives
+ /// Contains and TryTake. The condition site and the consumption site MUST both use
+ /// this identical predicate so they agree on the element they select.
+ ///
+ /// The rule whose pinned constant is sought.
+ /// The rule's target .
+ /// The lambda predicate, or when nothing enum-typed is pinned.
+ private static string ResolvePinnedConstantPredicate(TextualNotationRule referencedRule, IClass targetClass)
+ {
+ return TryResolvePinnedConstant(referencedRule, targetClass, out var propertyName, out var enumTypeName, out var literalName)
+ ? $"candidate => candidate.{propertyName} == {enumTypeName}.{literalName}"
+ : null;
+ }
+
+ ///
+ /// Resolves the components of a constant a rule pins to an enum-typed property through a
+ /// non-parsing assignment ({ kind = 'guard' }). Sibling rules sharing one target type are
+ /// distinguishable ONLY by such a constant, so it is the rule's role discriminator.
+ ///
+ /// The rule whose pinned constant is sought.
+ /// The rule's target .
+ /// The pinned property's C# name.
+ /// The fully-qualified name of the property's enumeration type.
+ /// The pinned enumeration literal's C# name.
+ /// true when the rule pins an enum-typed constant; false otherwise.
+ private static bool TryResolvePinnedConstant(TextualNotationRule referencedRule, IClass targetClass, out string propertyName, out string enumTypeName, out string literalName)
+ {
+ propertyName = null;
+ enumTypeName = null;
+ literalName = null;
+
var pinnedAssignment = referencedRule.Alternatives
.SelectMany(alternative => alternative.Elements)
.OfType()
@@ -383,19 +409,187 @@ private static string ResolvePinnedConstantPattern(TextualNotationRule reference
if (pinnedAssignment == null)
{
- return null;
+ return false;
}
var property = targetClass.QueryAllProperties()
.FirstOrDefault(x => string.Equals(x.Name, pinnedAssignment.PropertyName, StringComparison.OrdinalIgnoreCase));
if (property?.Type is not IEnumeration)
+ {
+ return false;
+ }
+
+ propertyName = property.Name.CapitalizeFirstLetter();
+ enumTypeName = property.Type.QueryFullyQualifiedTypeName();
+ literalName = pinnedAssignment.Value.Trim('\'').CapitalizeFirstLetter();
+ return true;
+ }
+
+ ///
+ /// Builds the non-consuming, role-based guard condition for a += assignment whose
+ /// referenced rule carries a role discriminator — {cursor}.Contains<T>(predicate) —
+ /// locating the element by ROLE instead of by cursor position. Returns
+ /// when no discriminating predicate resolves, so the caller falls back to positional access.
+ ///
+ /// The += assignment to build the condition for.
+ /// The class hosting the current rule (provides the UML cache).
+ /// The current .
+ /// The cursor variable the condition probes.
+ /// The Contains condition, or when not applicable.
+ private static string TryResolveRoleBasedContainsCondition(AssignmentElement assignmentElement, IClass umlClass, RuleGenerationContext ruleGenerationContext, string cursorVariableName)
+ {
+ if (assignmentElement.Value is not NonTerminalElement nonTerminalElement)
+ {
+ return null;
+ }
+
+ var referencedRule = ruleGenerationContext.FindRule(nonTerminalElement.Name);
+ var typeTarget = referencedRule?.EffectiveTarget;
+
+ if (typeTarget == null)
{
return null;
}
- var literalName = pinnedAssignment.Value.Trim('\'').CapitalizeFirstLetter();
- return $"{{ {property.Name.CapitalizeFirstLetter()}: {property.Type.QueryFullyQualifiedTypeName()}.{literalName} }}";
+ var targetClass = RuleQueryUtilities.FindClass(umlClass.Cache, typeTarget);
+ var targetTypeName = targetClass?.QueryFullyQualifiedTypeName();
+
+ if (targetTypeName == null)
+ {
+ return null;
+ }
+
+ var predicate = ResolveRoleBasedPredicate(referencedRule, targetClass, ruleGenerationContext);
+
+ return predicate == null ? null : $"{cursorVariableName}.Contains<{targetTypeName}>({predicate})";
+ }
+
+ ///
+ /// Resolves the role predicate for a referenced rule: the enum constant the rule pins takes
+ /// precedence (the metamodel-provided discriminator); otherwise the rule's structural signature
+ /// is synthesised from its body. Returns when neither is discriminating,
+ /// in which case the caller MUST stay positional — scanning ahead on a weak predicate steals
+ /// elements that belong to later consumptions.
+ ///
+ /// The rule whose role predicate is sought.
+ /// The rule's target .
+ /// The current .
+ /// The lambda predicate, or when the rule is not discriminating.
+ private static string ResolveRoleBasedPredicate(TextualNotationRule referencedRule, IClass targetClass, RuleGenerationContext ruleGenerationContext)
+ {
+ return ResolvePinnedConstantPredicate(referencedRule, targetClass)
+ ?? ResolveStructuralSignaturePredicate(referencedRule, targetClass, ruleGenerationContext);
+ }
+
+ ///
+ /// Synthesises a candidate-level structural-signature predicate from a rule's body: one clause
+ /// per mandatory discriminating assignment (owned-content shape or literal equality), clauses
+ /// AND-combined per alternative, alternatives OR-combined. EVERY alternative must contribute at
+ /// least one clause — an alternative without one admits any instance of the target type, which
+ /// would make the scan-ahead greedy — otherwise the whole rule is deemed non-discriminating.
+ ///
+ /// The rule whose structural signature is synthesised.
+ /// The rule's target (the candidate's type).
+ /// The current .
+ /// The lambda predicate, or when any alternative is non-discriminating.
+ private static string ResolveStructuralSignaturePredicate(TextualNotationRule referencedRule, IClass targetClass, RuleGenerationContext ruleGenerationContext)
+ {
+ var targetProperties = targetClass.QueryAllProperties();
+ var alternativeClauseSets = new List>();
+
+ foreach (var alternative in referencedRule.Alternatives)
+ {
+ // `A && A` is `A`, so dedupe as the OR-side already does.
+ var clauses = alternative.Elements
+ .Select(element => TryBuildCandidateSignatureClause(element, targetProperties, targetClass, ruleGenerationContext))
+ .Where(clause => clause != null)
+ .Distinct(StringComparer.Ordinal)
+ .ToList();
+
+ if (clauses.Count == 0)
+ {
+ return null;
+ }
+
+ alternativeClauseSets.Add(clauses);
+ }
+
+ // OR-ing a stronger conjunction with a weaker sibling is just the weaker sibling —
+ // `(B && A) || A` ≡ `A` — so drop every alternative whose clause set strictly contains
+ // another alternative's set.
+ var retainedClauseSets = alternativeClauseSets
+ .Where(clauseSet => !alternativeClauseSets.Any(otherSet => otherSet.Count < clauseSet.Count && otherSet.All(clauseSet.Contains)))
+ .ToList();
+
+ var distinctPredicates = retainedClauseSets
+ .Select(clauseSet => clauseSet.Count == 1 ? clauseSet[0] : $"({string.Join(" && ", clauseSet)})")
+ .Distinct(StringComparer.Ordinal)
+ .ToList();
+
+ var combined = distinctPredicates.Count == 1 ? distinctPredicates[0] : string.Join(" || ", distinctPredicates);
+
+ return $"candidate => {combined}";
+ }
+
+ ///
+ /// Builds one candidate-level signature clause for a rule-body element, or
+ /// when the element carries no discriminating information. Discriminating shapes: a mandatory
+ /// prop += NonTerminal owned-content projection (excluding root metaclasses, whose
+ /// OfType test is tautological) and a mandatory prop = 'literal' equality on an
+ /// enum- or string-typed property.
+ ///
+ /// The rule-body element to translate.
+ /// All properties of the candidate's target class.
+ /// The candidate's target .
+ /// The current .
+ /// The clause over candidate, or .
+ private static string TryBuildCandidateSignatureClause(RuleElement ruleElement, IEnumerable targetProperties, IClass targetClass, RuleGenerationContext ruleGenerationContext)
+ {
+ if (ruleElement is not AssignmentElement { IsOptional: false, IsCollection: false } assignment)
+ {
+ return null;
+ }
+
+ var matchingProperty = targetProperties.FirstOrDefault(property => string.Equals(property.Name, assignment.Property, StringComparison.OrdinalIgnoreCase));
+
+ if (matchingProperty == null)
+ {
+ return null;
+ }
+
+ switch (assignment)
+ {
+ case { Operator: "+=", Value: NonTerminalElement rhsNonTerminal } when matchingProperty.QueryIsEnumerable():
+ {
+ var rhsRule = ruleGenerationContext.FindRule(rhsNonTerminal.Name);
+ var rhsTarget = rhsRule?.EffectiveTarget ?? rhsNonTerminal.Name;
+ var rhsClass = RuleQueryUtilities.FindClass(targetClass.Cache, rhsTarget);
+
+ // OfType over a root metaclass admits ANY populated collection — no discrimination.
+ if (rhsClass == null || rhsClass.Name is "Element" or "Relationship")
+ {
+ return null;
+ }
+
+ return $"candidate.{matchingProperty.QueryPropertyNameBasedOnUmlProperties()}.OfType<{rhsClass.QueryFullyQualifiedTypeName()}>().Any()";
+ }
+
+ case { Operator: "=", Value: TerminalElement literalTerminal } when !string.IsNullOrWhiteSpace(literalTerminal.Value):
+ {
+ if (matchingProperty.Type is IEnumeration)
+ {
+ return $"candidate.{matchingProperty.Name.CapitalizeFirstLetter()} == {matchingProperty.Type.QueryFullyQualifiedTypeName()}.{literalTerminal.Value.Trim('\'').CapitalizeFirstLetter()}";
+ }
+
+ return matchingProperty.QueryIsString()
+ ? $"candidate.{matchingProperty.QueryPropertyNameBasedOnUmlProperties()} == \"{literalTerminal.Value}\""
+ : null;
+ }
+
+ default:
+ return null;
+ }
}
///
@@ -492,14 +686,36 @@ private void ProcessSingleAlternative(EncodedTextWriter writer, IClass umlClass,
if (consumptionAssignments.Count > 1)
{
var conditionParts = new List();
+ var emittedPinnedCondition = false;
for (var consumptionIndex = 0; consumptionIndex < consumptionAssignments.Count; consumptionIndex++)
{
+ var consumptionAssignment = consumptionAssignments[consumptionIndex];
+
+ // A role-based part holds regardless of cursor position, so a positional tail-walk conjunct no
+ // longer lines up with it and would reintroduce the silent drop.
+ if (emittedPinnedCondition && !elements.Contains(consumptionAssignment))
+ {
+ continue;
+ }
+
+ // A discriminating rule is located by ROLE (Contains + predicate),
+ // not by cursor position — its consumption site takes the element
+ // with TryTake and the identical predicate.
+ var pinnedCondition = TryResolveRoleBasedContainsCondition(consumptionAssignment, umlClass, ruleGenerationContext, iterator.CursorVariableName);
+
+ if (pinnedCondition != null)
+ {
+ conditionParts.Add(pinnedCondition);
+ emittedPinnedCondition = true;
+ continue;
+ }
+
var cursorAccess = consumptionIndex == 0
? $"{iterator.CursorVariableName}.Current"
: $"{iterator.CursorVariableName}.GetNext({consumptionIndex})";
- var typeName = ResolveAssignmentTargetTypeName(consumptionAssignments[consumptionIndex], umlClass, ruleGenerationContext);
+ var typeName = ResolveAssignmentTargetTypeName(consumptionAssignment, umlClass, ruleGenerationContext);
conditionParts.Add(typeName == null
? $"{cursorAccess} != null"
@@ -510,14 +726,14 @@ private void ProcessSingleAlternative(EncodedTextWriter writer, IClass umlClass,
}
else
{
- // 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 pinnedSingleCondition = TryResolveRoleBasedContainsCondition(assigment, umlClass, ruleGenerationContext, iterator.CursorVariableName);
+
+ // Guard on the consumed TYPE: a bare non-null test emits the group's terminals spuriously.
var singleTypeName = ResolveAssignmentTargetTypeName(assigment, umlClass, ruleGenerationContext);
- ifStatementContent.Add(singleTypeName == null
+ ifStatementContent.Add(pinnedSingleCondition ?? (singleTypeName == null
? property.QueryIfStatementContentForNonEmpty(iterator.CursorVariableName)
- : $"{iterator.CursorVariableName}.Current is {singleTypeName}");
+ : $"{iterator.CursorVariableName}.Current is {singleTypeName}"));
}
}
}
@@ -576,6 +792,15 @@ private void ProcessSingleAlternative(EncodedTextWriter writer, IClass umlClass,
}
}
+ // A nested optional group can carry the only notation for its content, so the outer group must
+ // be entered whenever that content is present.
+ var nestedGroupCondition = TryResolveNestedGroupCursorCondition(umlClass, elements, ruleGenerationContext);
+
+ if (nestedGroupCondition != null)
+ {
+ inlineConditionParts.Add(nestedGroupCondition);
+ }
+
var optionalCollectionCondition = TryResolveOptionalCollectionGroupCondition(umlClass, elements, ruleGenerationContext);
if (optionalCollectionCondition != null)
@@ -610,15 +835,40 @@ private void ProcessSingleAlternative(EncodedTextWriter writer, IClass umlClass,
}
///
- /// Resolves the guard for an optional group whose only variable content is a *-quantified
- /// bare non-terminal — e.g. ( '{' ActionBodyItem* '}' )?. Such a group must be emitted only
- /// when its loop would iterate at least once: the group's own terminals carry no information, so a
- /// property-based condition wrongly emits an empty { } whenever any unrelated property is set.
+ /// Builds the condition testing whether a NESTED optional group inside an optional group has
+ /// content on the cursor — the outer group must be entered to emit it, even when the outer
+ /// group's own elements would all be empty.
///
- /// The related
- /// The optional group's elements
- /// The current
- /// The cursor-based condition, or when the group is not that shape.
+ /// The class hosting the current rule (provides the UML cache).
+ /// The outer optional group's elements.
+ /// The current .
+ /// The condition, or when no nested group consumes a cursor.
+ private static string TryResolveNestedGroupCursorCondition(IClass umlClass, List elements, RuleGenerationContext ruleGenerationContext)
+ {
+ var nestedAssignment = elements
+ .OfType()
+ .Where(group => group.IsOptional)
+ .SelectMany(group => group.Alternatives.SelectMany(groupAlternative => groupAlternative.Elements))
+ .OfType()
+ .FirstOrDefault(assignment => assignment is { Operator: "+=", Value: NonTerminalElement });
+
+ if (nestedAssignment == null)
+ {
+ return null;
+ }
+
+ var cursor = ruleGenerationContext.DefinedCursors.FirstOrDefault(definedCursor => definedCursor.ApplicableRuleElements.Contains(nestedAssignment));
+
+ if (cursor == null)
+ {
+ return null;
+ }
+
+ var typeName = ResolveAssignmentTargetTypeName(nestedAssignment, umlClass, ruleGenerationContext);
+
+ return typeName == null ? null : $"{cursor.CursorVariableName}.Current is {typeName}";
+ }
+
private static string TryResolveOptionalCollectionGroupCondition(IClass umlClass, List elements, RuleGenerationContext ruleGenerationContext)
{
var nonTerminals = elements.OfType().ToList();
@@ -643,8 +893,6 @@ private static string TryResolveOptionalCollectionGroupCondition(IClass umlClass
return null;
}
- // The cursor is declared up-front by DeclareAllRequiredCursors; if it is absent this is not the
- // shape we handle, so fall back rather than emit a second declaration.
var existingCursor = ruleGenerationContext.DefinedCursors.SingleOrDefault(x => x.IsCursorValidForProperty(targetProperty));
if (existingCursor == null)
@@ -885,20 +1133,17 @@ private void EmitNonTerminalThenAssignmentDispatch(EncodedTextWriter writer, ICl
/// The current
private void ProcessMultiElementAlternatives(EncodedTextWriter writer, IClass umlClass, IReadOnlyCollection alternatives, RuleGenerationContext ruleGenerationContext)
{
- // When all alternatives consist exclusively of terminal elements (and optionally non-parsing assignments), handle via code-gen
if (alternatives.All(alt => alt.Elements.Count > 0 && alt.Elements.All(element => element is TerminalElement or NonParsingAssignmentElement)))
{
EmitTerminalOnlyAlternatives(writer, umlClass, alternatives, ruleGenerationContext);
return;
}
- // Detect pattern: property=[QualifiedName] | property=NonTerminal{containment+=property}
if (alternatives.Count == 2 && TryEmitQualifiedNameOrChainAlternatives(writer, umlClass, alternatives, ruleGenerationContext))
{
return;
}
- // Multi-element alternatives (e.g., ';' | '{' NamespaceBodyElement* '}')
var firstAlt = alternatives.ElementAt(0);
var hasTerminalOnlyFirstAlt = firstAlt.Elements.Count == 1 && firstAlt.Elements[0] is TerminalElement;
@@ -908,7 +1153,6 @@ private void ProcessMultiElementAlternatives(EncodedTextWriter writer, IClass um
}
else
{
- // Try each pattern handler in order; fall back to HandCoded if none match
if (this.TryHandleOperatorLiteralAlternation(writer, umlClass, alternatives, ruleGenerationContext))
{
return;
@@ -1067,16 +1311,8 @@ private static bool TryEmitQualifiedNameOrChainAlternatives(EncodedTextWriter wr
var chainTypeName = chainTargetClass.QueryFullyQualifiedTypeName();
var chainVarName = $"chained{resolvedPropertyName}As{chainTargetClass.Name}";
- string builderCallString;
-
- if (typeTarget == ruleGenerationContext.NamedElementToGenerate.Name)
- {
- builderCallString = $"Build{chainNonTerminal.Name}({chainVarName}, writerContext, stringBuilder);";
- }
- else
- {
- builderCallString = $"{typeTarget}TextualNotationBuilder.Build{chainNonTerminal.Name}({chainVarName}, writerContext, stringBuilder);";
- }
+ var builderCallString = typeTarget == ruleGenerationContext.NamedElementToGenerate.Name ? $"Build{chainNonTerminal.Name}({chainVarName}, writerContext, stringBuilder);"
+ : $"{typeTarget}TextualNotationBuilder.Build{chainNonTerminal.Name}({chainVarName}, writerContext, stringBuilder);";
writer.WriteSafeString($"if ({variableName}.{resolvedContainmentName}.Contains({variableName}.{resolvedPropertyName}) && {variableName}.{resolvedPropertyName} is {chainTypeName} {chainVarName}){Environment.NewLine}");
writer.WriteSafeString($"{{{Environment.NewLine}");
@@ -1086,10 +1322,8 @@ private static bool TryEmitQualifiedNameOrChainAlternatives(EncodedTextWriter wr
writer.WriteSafeString($"{{{Environment.NewLine}");
writer.WriteSafeString($"SharedTextualNotationBuilder.AppendQualifiedName(stringBuilder,{variableName}.{resolvedPropertyName}, writerContext, poco);{Environment.NewLine}");
- // 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.
+ // Both alternatives denote the same notational prefix, so a trailing terminal must be emitted on
+ // both. The KEBNF omits it on one and is immutable, so it is recovered here.
var chainTrailingTerminal = QueryTrailingTerminal(referencedRule);
if (chainTrailingTerminal == null)
@@ -1195,14 +1429,7 @@ private bool TryEmitSubclassRuleDispatchAlternatives(EncodedTextWriter writer, I
// A NonTerminal-valued assignment emits its own null guard inside ProcessAssignmentElement;
// only a value-literal assignment (e.g. [QualifiedName]) needs the guard supplied here.
- if (assignmentElement.Value is ValueLiteralElement)
- {
- writer.WriteSafeString($"else if ({targetProperty.QueryIfStatementContentForNonEmpty(variableName)}){Environment.NewLine}");
- }
- else
- {
- writer.WriteSafeString($"else{Environment.NewLine}");
- }
+ writer.WriteSafeString(assignmentElement.Value is ValueLiteralElement ? $"else if ({targetProperty.QueryIfStatementContentForNonEmpty(variableName)}){Environment.NewLine}" : $"else{Environment.NewLine}");
writer.WriteSafeString($"{{{Environment.NewLine}");
this.ProcessAssignmentElement(writer, umlClass, ruleGenerationContext, assignmentElement, true);
@@ -1441,14 +1668,8 @@ private void EmitTerminalVsBodyWithCollectionNonTerminals(EncodedTextWriter writ
? $".IsValidFor{collectionNonTerminals[0].Name}(writerContext)"
: string.Empty;
- if (requiresIsValidForGuard)
- {
- writer.WriteSafeString($"if (writerContext.CursorCache.GetOrCreateCursor(poco.Id, \"{targetProperty.Name}\", poco.{propertyAccessName}).Current is not SysML2.NET.Core.POCO.Root.Elements.IRelationship emptyBodyCandidate || !emptyBodyCandidate{guardCallSuffix}){Environment.NewLine}");
- }
- else
- {
- writer.WriteSafeString($"if(writerContext.CursorCache.GetOrCreateCursor(poco.Id, \"{targetProperty.Name}\", poco.{propertyAccessName}).Current == null){Environment.NewLine}");
- }
+ writer.WriteSafeString(requiresIsValidForGuard ? $"if (writerContext.CursorCache.GetOrCreateCursor(poco.Id, \"{targetProperty.Name}\", poco.{propertyAccessName}).Current is not SysML2.NET.Core.POCO.Root.Elements.IRelationship emptyBodyCandidate || !emptyBodyCandidate{guardCallSuffix}){Environment.NewLine}"
+ : $"if(writerContext.CursorCache.GetOrCreateCursor(poco.Id, \"{targetProperty.Name}\", poco.{propertyAccessName}).Current == null){Environment.NewLine}");
writer.WriteSafeString($"{{{Environment.NewLine}");
writer.WriteSafeString($"stringBuilder.AppendLine(\"{terminalValue}\");{Environment.NewLine}");
@@ -1596,37 +1817,11 @@ private void EmitTerminalVsBodyWithSingleNonTerminal(EncodedTextWriter writer, I
/// by an IsValidFor{Rule} predicate instead of a bare null-test.
///
///
- /// Allowlisted by name, deliberately, because the four entries encode TWO unrelated concerns and no
- /// single predicate can derive both:
- ///
- /// -
- /// CaseBodyItem, DefinitionBodyItem
- /// A trailing consumer reads the SAME cursor after the loop, so an unguarded loop
- /// swallows it — CaseBody's own ( ownedRelationship += ResultExpressionMember )?, and
- /// PortDefinition's trailing ConjugatedPortDefinitionMember reached through
- /// Definition → DefinitionBody. This is a property OF THE GRAMMAR and
- /// derives it.
- ///
- /// -
- /// InterfaceBodyItem, ActionBodyItem
- /// The item builder declines an element it cannot render WITHOUT advancing the cursor
- /// — SharedTextualNotationBuilder's default: arm, and ActionBodyItem's outer
- /// if (IsValidForActionBodyItem). The guard is what keeps such an element from ever reaching
- /// the dispatcher. InterfaceBody is the clean witness that this is NOT the grammar concern:
- /// it is the last element of both InterfaceDefinition and InterfaceUsage, so no
- /// trailing consumer exists, yet the guard is still load-bearing.
- ///
- ///
- /// The second concern is not derivable. It depends on the internal control flow of a
- /// hand-written method: BuildStateBodyItemHandCoded is equally hand-coded yet DRAINS the
- /// cursor in its own while, so it can never stall its caller — a "the item builder is
- /// hand-coded" heuristic would over-guard it. Deciding it would mean analysing that C#.
- /// What removes the risk instead is CollectionCursor.AssertAdvancedSince, emitted by
- /// at the foot of every generated cursor loop: a stalled
- /// iteration now throws immediately instead of hanging. That matters because a hang is invisible to
- /// a corpus that compares output — dropping InterfaceBodyItem from this list once produced a
- /// fully green 33-case run. With the assertion in place this allowlist governs OUTPUT CORRECTNESS
- /// (do not swallow the result expression, do not emit a bare ref;) rather than termination.
+ /// Allowlisted by name because the entries encode two unrelated concerns: a trailing consumer reading
+ /// the same cursor after the loop (derivable, see ), and an
+ /// item builder that declines an element without advancing the cursor (not derivable — it depends on
+ /// the control flow of a hand-written method). The allowlist governs output correctness;
+ /// CollectionCursor.AssertAdvancedSince covers termination.
///
///
/// Emits the capture of a cursor's position immediately before a loop body, and returns the name of
@@ -1673,20 +1868,27 @@ private static bool IsGuardedBodyItemRule(string bodyItemRuleName)
/// be supplied by a hand-coded IsValidFor{Rule} guard.
///
///
- /// Currently FunctionOperationExpression. Its arm in NonFeatureChainPrimaryExpression
+ /// DefaultInterfaceEnd. Its own body yields only isEnd ?= 'end', but SysML 2.0
+ /// §7.14.2 makes the port keyword optional on an interface end "if no owned cross feature is
+ /// declared on the end" — so an end carrying one must fall through to the sibling
+ /// StructureUsageElement arm, which reaches PortUsage and its EndUsagePrefix
+ /// cross-feature slot. An absence constraint on the element's own contents is not expressible as a
+ /// body-shape predicate.
+ /// FunctionOperationExpression. Its arm in NonFeatureChainPrimaryExpression
/// targets InvocationExpression and sits above the SequenceExpression arm, which
/// targets the supertype Expression. A sequence (a, b, c) is an OperatorExpression
/// with operator = "," — hence an InvocationExpression whose first owned relationship is
/// an IParameterMembership, which is all the unguarded arm tested — so every sequence was
/// swallowed and rendered with a spurious ->. Telling the two apart needs the SECOND owned
/// relationship (Membership for x->f(), ParameterMembership for a sequence),
- /// i.e. cursor lookahead, which no body-shape analysis can produce.
+ /// i.e. cursor lookahead, which no body-shape analysis can produce.
///
/// The KEBNF rule name of the alternative
/// true if the codegen should emit a hand-coded IsValidFor{Rule} guard
private static bool RequiresHandCodedAlternativeGuard(string alternativeRuleName)
{
- return string.Equals(alternativeRuleName, "FunctionOperationExpression", StringComparison.Ordinal);
+ return string.Equals(alternativeRuleName, "FunctionOperationExpression", StringComparison.Ordinal)
+ || string.Equals(alternativeRuleName, "DefaultInterfaceEnd", StringComparison.Ordinal);
}
///
diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleQueryUtilities.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleQueryUtilities.cs
index 59ef7d791..4b64de28a 100644
--- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleQueryUtilities.cs
+++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleQueryUtilities.cs
@@ -145,6 +145,33 @@ internal static List QueryBooleanAssignmentProperties(TextualNotationRul
return result;
}
+ ///
+ /// Collects every ?= property a rule can set, INCLUDING the conditional ones
+ /// (inside an optional / repeating / alternation position).
+ ///
+ /// The rule to inspect
+ /// All available rules for resolving NonTerminal references
+ /// The property names the rule may set.
+ ///
+ /// This is the set to test a SIBLING against when deciding whether a boolean is a valid
+ /// discriminator: a property the sibling merely MAY set is still one it can satisfy at runtime,
+ /// so guarding on it lets the first arm claim the sibling's instances. PortionUsage takes
+ /// ( isIndividual ?= 'individual' )? optionally while IndividualUsage takes it
+ /// unconditionally — testing only unconditional assignments made isIndividual look unique
+ /// and every individual snapshot was written as a plain individual.
+ ///
+ internal static List QueryAllBooleanAssignmentProperties(TextualNotationRule rule, IReadOnlyList allRules)
+ {
+ var result = new List();
+
+ foreach (var alternative in rule.Alternatives)
+ {
+ CollectBooleanAssignmentProperties(alternative.Elements, allRules, result, new HashSet(), isConditional: false, includeConditional: true);
+ }
+
+ return result;
+ }
+
///
/// Recursively collects boolean ?= assignment property names from a
/// list of . Only assignments at
@@ -161,13 +188,17 @@ internal static List QueryBooleanAssignmentProperties(TextualNotationRul
/// repeating / alternation group (directly or transitively) so any
/// ?= reached from here cannot serve as a guaranteed discriminator.
///
- internal static void CollectBooleanAssignmentProperties(IReadOnlyList elements, IReadOnlyList allRules, List result, HashSet visited, bool isConditional)
+ ///
+ /// true to collect conditional ?= assignments as well — used when testing what a
+ /// SIBLING alternative may set; see .
+ ///
+ internal static void CollectBooleanAssignmentProperties(IReadOnlyList elements, IReadOnlyList allRules, List result, HashSet visited, bool isConditional, bool includeConditional = false)
{
foreach (var element in elements)
{
switch (element)
{
- case AssignmentElement { Operator: "?=" } assignment when !isConditional:
+ case AssignmentElement { Operator: "?=" } assignment when includeConditional || !isConditional:
result.Add(assignment.Property);
break;
@@ -180,7 +211,7 @@ internal static void CollectBooleanAssignmentProperties(IReadOnlyList(() => NullImpliedRelationshipProvider.Instance));
+
+ // KerML 7.4.7.2 matches a subclassifier's OWNED parameters against each superclassifier's
+ // `parameter`, which is `directedFeature` — so `base` contributes its inherited parameters,
+ // ordered after the one it owns, and the return parameter is excluded on both sides.
+ var baseBehavior = new Behavior { Id = Guid.NewGuid() };
+ var baseFirst = AddParameter(baseBehavior);
+ var baseSecond = AddParameter(baseBehavior);
+
+ var middle = new Behavior { Id = Guid.NewGuid() };
+ var middleOwned = AddParameter(middle);
+ var middleResult = AddResult(middle);
+ middleResult.Direction = FeatureDirectionKind.Out;
+ Specialize(middle, baseBehavior);
+
+ var subtype = new Behavior { Id = Guid.NewGuid() };
+ var first = AddParameter(subtype);
+ var second = AddParameter(subtype);
+ var third = AddParameter(subtype);
+ var result = AddResult(subtype);
+ result.Direction = FeatureDirectionKind.Out;
+ Specialize(subtype, middle);
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(rule.ConstraintName, Is.EqualTo("checkFeatureParameterRedefinition"));
+
+ // `parameter` is every directed Feature, so it carries the return parameter and the
+ // inherited ones; the constraint rejects only the former.
+ Assert.That(middle.parameter, Is.EqualTo(new[] { middleOwned, middleResult, baseFirst, baseSecond }));
+
+ Assert.That(RedefinedBy(rule, first), Is.EqualTo(new[] { middleOwned }));
+ Assert.That(RedefinedBy(rule, second), Is.EqualTo(new[] { baseFirst }));
+ Assert.That(RedefinedBy(rule, third), Is.EqualTo(new[] { baseSecond }));
+
+ // A return parameter is not a parameter for this constraint, on either side.
+ Assert.That(rule.Apply(result), Is.Empty);
+
+ Assert.That(() => rule.Apply(null), Throws.TypeOf());
+ Assert.That(rule.Apply(new Feature { Id = Guid.NewGuid() }), Is.Empty);
+ Assert.That(rule.Apply(AddParameter(new Classifier { Id = Guid.NewGuid() })), Is.Empty);
+ }
+ }
+
[Test]
public void VerifyFeatureResultRedefinitionRule()
{
@@ -208,7 +257,7 @@ public void VerifyEveryRedefinitionConstraintIsCoveredOrDeliberatelyNot()
[
new FeatureEndRedefinitionRule(this.factory),
new FeatureResultRedefinitionRule(this.factory),
- new FeatureParameterRedefinitionRule(this.factory),
+ new FeatureParameterRedefinitionRule(this.factory, new Lazy(() => NullImpliedRelationshipProvider.Instance)),
new RequirementUsageObjectiveRedefinitionRule(this.factory),
new AssignmentActionUsageReferentRedefinitionRule(this.factory),
new FeatureChainExpressionSourceTargetRedefinitionRule(this.factory),
@@ -271,6 +320,14 @@ private static Feature AddResult(IType owner)
return result;
}
+ private static Feature AddParameter(IType owner)
+ {
+ var parameter = new Feature { Id = Guid.NewGuid(), Direction = FeatureDirectionKind.In };
+ Own(owner, parameter, new FeatureMembership { Id = Guid.NewGuid() });
+
+ return parameter;
+ }
+
private static void Own(IElement owner, IElement owned, IRelationship membership = null)
{
var relationship = membership ?? new FeatureMembership { Id = Guid.NewGuid() };
diff --git a/SysML2.NET.Semantics/Extensions/ServiceCollectionExtensions.cs b/SysML2.NET.Semantics/Extensions/ServiceCollectionExtensions.cs
index f14b12c01..84b542f8d 100644
--- a/SysML2.NET.Semantics/Extensions/ServiceCollectionExtensions.cs
+++ b/SysML2.NET.Semantics/Extensions/ServiceCollectionExtensions.cs
@@ -73,6 +73,7 @@ public static IServiceCollection AddSysML2Semantics(this IServiceCollection serv
services.AddScoped();
services.AddScoped(serviceProvider => new ImpliedRuleGuardRegistry(serviceProvider.GetServices()));
services.AddScoped();
+ services.AddScoped(serviceProvider => new Lazy(serviceProvider.GetRequiredService));
services.AddImpliedRelationshipRule();
services.AddImpliedRelationshipRule();
diff --git a/SysML2.NET.Semantics/Implied/ImpliedRelationshipProvider.cs b/SysML2.NET.Semantics/Implied/ImpliedRelationshipProvider.cs
index 198f0f94f..476b14b77 100644
--- a/SysML2.NET.Semantics/Implied/ImpliedRelationshipProvider.cs
+++ b/SysML2.NET.Semantics/Implied/ImpliedRelationshipProvider.cs
@@ -285,6 +285,10 @@ private static IReadOnlyList ApplyRule(IImpliedRelationshipRule r
{
return [];
}
+ catch (NotSupportedException)
+ {
+ return [];
+ }
}
///
diff --git a/SysML2.NET.Semantics/Implied/Rules/FeatureParameterRedefinitionRule.cs b/SysML2.NET.Semantics/Implied/Rules/FeatureParameterRedefinitionRule.cs
index 0d0a07545..8bfd60c6f 100644
--- a/SysML2.NET.Semantics/Implied/Rules/FeatureParameterRedefinitionRule.cs
+++ b/SysML2.NET.Semantics/Implied/Rules/FeatureParameterRedefinitionRule.cs
@@ -42,9 +42,9 @@ namespace SysML2.NET.Semantics.Implied.Rules
/// ->select(direction <> null)->reject(owningFeatureMembership.oclIsKindOf(
/// ReturnParameterMembership)) in … ownedParameters->size() >= i implies
/// redefines(ownedParameters->at(i)).
- /// Parameters are the directed owned Features EXCLUDING the return parameter, matched positionally
- /// against the supertype's. An InvocationExpression that already declares an explicit (non-implied)
- /// Redefinition is excluded, since the modeller has bound its arguments by hand.
+ /// The subtype contributes its directed OWNED Features excluding the return parameter; the supertype
+ /// contributes its parameter, matched positionally. An InvocationExpression that already declares an
+ /// explicit (non-implied) Redefinition is excluded, since the modeller has bound its arguments by hand.
///
public class FeatureParameterRedefinitionRule : IImpliedRelationshipRule
{
@@ -53,14 +53,22 @@ public class FeatureParameterRedefinitionRule : IImpliedRelationshipRule
///
private readonly IImpliedRelationshipFactory factory;
+ ///
+ /// The provider supplying the owning Type's implied Specializations, resolved lazily because it is
+ /// the same provider this rule is registered with.
+ ///
+ private readonly Lazy provider;
+
///
/// Initializes a new instance of the class.
///
/// The factory creating the detached Redefinitions.
- /// Thrown when is null.
- public FeatureParameterRedefinitionRule(IImpliedRelationshipFactory factory)
+ /// The provider supplying the owning Type's implied Specializations.
+ /// Thrown when or is null.
+ public FeatureParameterRedefinitionRule(IImpliedRelationshipFactory factory, Lazy provider)
{
this.factory = factory ?? throw new ArgumentNullException(nameof(factory));
+ this.provider = provider ?? throw new ArgumentNullException(nameof(provider));
}
///
@@ -96,15 +104,29 @@ public IReadOnlyList Apply(IElement element)
return
[
- ..parameter.owningType.ownedSpecialization
- .Select(specialization => specialization.General)
+ ..this.QuerySupertypes(parameter.owningType)
.Where(supertype => supertype is IBehavior or IStep)
- .Select(QueryParameters)
+ .Select(QuerySupertypeParameters)
.Where(supertypeParameters => supertypeParameters.Count > position)
.Select(supertypeParameters => this.factory.CreateImpliedRedefinition(parameter, supertypeParameters[position]))
];
}
+ ///
+ /// Returns the direct supertypes of a Type over the effective model: those its declared
+ /// Specializations reach, together with those its implied Specializations reach (KerML §8.4.2).
+ ///
+ /// The Type owning the parameter.
+ /// The direct supertypes, declared ones first.
+ private IEnumerable QuerySupertypes(IType owningType)
+ {
+ return owningType.ownedSpecialization
+ .Concat(this.provider.Value.GetImpliedSpecializations(owningType))
+ .Select(specialization => specialization.General)
+ .Where(supertype => supertype != null && !ReferenceEquals(supertype, owningType))
+ .Distinct();
+ }
+
///
/// Asserts whether a Type's parameters are subject to the constraint.
///
@@ -131,5 +153,28 @@ private static List QueryParameters(IType type)
.Where(ownedFeature => ownedFeature.Direction.HasValue)
.Where(ownedFeature => ownedFeature.owningFeatureMembership is not IReturnParameterMembership)];
}
+
+ ///
+ /// Returns the parameters a supertype offers for positional matching, excluding the return parameter.
+ ///
+ /// The supertype to inspect.
+ /// The parameters, owned ones first and inherited ones after.
+ ///
+ /// The supertype side is parameter — which KerML defines as directedFeature, so inherited
+ /// parameters count and are ordered after owned ones — because §7.4.7.2 requires a subclassifier's owned
+ /// parameters to redefine "the parameter at the same position" of each superclassifier. The constraint's
+ /// OCL formalises this side as ownedFeature, which is narrower than the clause it formalises.
+ ///
+ private static List QuerySupertypeParameters(IType supertype)
+ {
+ var parameters = supertype switch
+ {
+ IBehavior behavior => behavior.parameter,
+ IStep step => step.parameter,
+ _ => supertype.directedFeature
+ };
+
+ return [..parameters.Where(parameter => parameter.owningFeatureMembership is not IReturnParameterMembership)];
+ }
}
}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/01-Parts Tree/1a-Parts Tree.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/01-Parts Tree/1a-Parts Tree.sysml
index 4acd7af5b..3e1f0ea56 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/01-Parts Tree/1a-Parts Tree.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/01-Parts Tree/1a-Parts Tree.sysml
@@ -5,9 +5,9 @@ package '1a-Parts Tree' {
attribute mass :> ISQ::mass {
doc
/*
- * The 'mass' attribute property is declared here to be a
- * specialization (subset) of the general 'mass' quantity
- * from the 'ISQ' (International System of Quantities)
+ * The 'mass' attribute property is declared here to be a
+ * specialization (subset) of the general 'mass' quantity
+ * from the 'ISQ' (International System of Quantities)
* library model.
*/
@@ -35,7 +35,7 @@ package '1a-Parts Tree' {
*/
attribute mass :>> Vehicle::mass = 1750[kg] {
/*
- * This redefines the 'mass' attribute property from 'Vehicle' to
+ * This redefines the 'mass' attribute property from 'Vehicle' to
* give it a fixed attribute.
*/
}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/01-Parts Tree/1c-Parts Tree Redefinition.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/01-Parts Tree/1c-Parts Tree Redefinition.sysml
index 61591ce10..f85aa6757 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/01-Parts Tree/1c-Parts Tree Redefinition.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/01-Parts Tree/1c-Parts Tree Redefinition.sysml
@@ -34,7 +34,7 @@ package '1c-Parts Tree Redefinition' {
}
part vehicle1_c1 :> vehicle1 {
/*
- * 'vehicle1_c1' is a specialization of 'vehicle1' (technically
+ * 'vehicle1_c1' is a specialization of 'vehicle1' (technically
* a subset). It inherits all the parts of 'vehicle1' and
* only needs to specify additional or redefined parts.
*/
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/01-Parts Tree/1d-Parts Tree with Reference.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/01-Parts Tree/1d-Parts Tree with Reference.sysml
index 5169c7330..458f38171 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/01-Parts Tree/1d-Parts Tree with Reference.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/01-Parts Tree/1d-Parts Tree with Reference.sysml
@@ -13,7 +13,7 @@ package '1d-Parts Tree with Reference' {
ref hitchBall: HitchBall {
/*
* 'vehicle1_c1'::'hitchBall' is a reference property that
- * references a hitch ball that is not part of this vehicle.
+ * references a hitch ball that is not part of this vehicle.
* If 'vehicle1_c1' is removed or destroyed, this does not
* effect the hitchBall referenced here.
*/
@@ -35,7 +35,7 @@ package '1d-Parts Tree with Reference' {
* This is a shorthand for a binding connector between the
* 'trailerCoupler' here and the 'trailerCoupler' in 'trailerHitch'.
* The binding connector is now contained within the 'trailer1'
- * part, though, rather than being at the system level.
+ * part, though, rather than being at the system level.
*/
}
}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/02-Parts Interconnection/2a-Parts Interconnection.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/02-Parts Interconnection/2a-Parts Interconnection.sysml
index 54ca2d4ec..7763424bb 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/02-Parts Interconnection/2a-Parts Interconnection.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/02-Parts Interconnection/2a-Parts Interconnection.sysml
@@ -84,7 +84,7 @@ package '2a-Parts Interconnection' {
part engine: Engine;
interface : EngineToTransmissionInterface connect engine.drivePwrPort to transmission.clutchPort {
/*
- * A usage of an interface definition connects two ports relative to
+ * A usage of an interface definition connects two ports relative to
* a containing context.
*/
}
@@ -117,7 +117,7 @@ package '2a-Parts Interconnection' {
}
interface differential.leftDiffPort to rearAxle.leftHalfAxle.axleToDiffPort {
/*
- * A connection can be to a port that is arbitrarily deeply nested, on either end.
+ * A connection can be to a port that is arbitrarily deeply nested, on either end.
*/
}
interface differential.rightDiffPort to rearAxle.rightHalfAxle.axleToDiffPort;
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
index 4789b46c0..622fcc4de 100644
--- 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
@@ -9,7 +9,7 @@ package '4a-Functional Allocation' {
perform 'provide power' {
doc
/*
- * This allocates the action '3a-Function-based Behavior-1'::'provide power' as an enacted
+ * This allocates the action '3a-Function-based Behavior-1'::'provide power' as an enacted
* performance of 'vehicle_c1_functional_allocation'.
*/
@@ -21,7 +21,7 @@ package '4a-Functional Allocation' {
}
perform 'provide power'.'generate torque' {
/*
- * This allocates one of the sub-steps of 'provide power' to a sub-part of vehicle_c1.
+ * 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;
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-1.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-1.sysml
index 9b649bd01..ff28c2da4 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-1.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-1.sysml
@@ -38,7 +38,7 @@ package '5-State-based Behavior-1' {
package Usages {
private import Definitions::*;
/*
- * These actions are used enabled in the state usage
+ * These actions are used enabled in the state usage
* 'vehicle states', in addition to 'provide power'.
*/
action 'perform self test': 'Perform Self Test';
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-1a.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-1a.sysml
index 55998edf9..ff1cb8d12 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-1a.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-1a.sysml
@@ -39,7 +39,7 @@ package '5-State-based Behavior-1a' {
package Usages {
private import Definitions::*;
/*
- * These actions are used enabled in the state usage
+ * These actions are used enabled in the state usage
* 'vehicle states', in addition to 'provide power'.
*/
action 'provide power': 'Provide Power';
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-2.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-2.sysml
index 2e7a7f0d1..09fabdb80 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-2.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-2.sysml
@@ -35,7 +35,7 @@ package '5-State-based Behavior-2' {
entry;
then off;
/*
- * The following uses a shorthand for a transition whose source
+ * The following uses a shorthand for a transition whose source
* is the immediately preceding state.
*/
state off;
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/06-Individual and Snapshots/6-Individual and Snapshots.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/06-Individual and Snapshots/6-Individual and Snapshots.sysml
index 21fe49047..3bdc641e4 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/06-Individual and Snapshots/6-Individual and Snapshots.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/06-Individual and Snapshots/6-Individual and Snapshots.sysml
@@ -91,8 +91,8 @@ package '6-Individual and Snapshots' {
:>> acceleration = a0;
exhibit vehicleStates.on {
/*
- * This asserts that the snapshot exhibits the referenced
- * state, which means that the vehicle must me in the state
+ * This asserts that the snapshot exhibits the referenced
+ * state, which means that the vehicle must me in the state
* at the time of the snapshot.
*/
}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/07-Variant Configuration/7a1-Variant Configuration - General Concept-a.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/07-Variant Configuration/7a1-Variant Configuration - General Concept-a.sysml
index dacbec9db..f8f28bb21 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/07-Variant Configuration/7a1-Variant Configuration - General Concept-a.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/07-Variant Configuration/7a1-Variant Configuration - General Concept-a.sysml
@@ -49,7 +49,7 @@ package '7a1-Variant Configuration - General Concept-a' {
part :>> subsystemA = subsystemA::subsystem1;
part :>> subsystemB = subsystemB::subsystem3 {
part :>> part5 {
- perform action :>> doXorY = '7a1-Variant Configuration - General Concept-a'::doX;
+ perform action :>> doXorY = doX;
}
}
}
@@ -57,7 +57,7 @@ package '7a1-Variant Configuration - General Concept-a' {
part :>> subsystemA = subsystemA::subsystem2;
part :>> subsystemB = subsystemB::subsystem4 {
part :>> part5 {
- perform action :>> doXorY = '7a1-Variant Configuration - General Concept-a'::doY;
+ perform action :>> doXorY = doY;
}
}
}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/14-Language Extensions/14a-Language Extensions.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/14-Language Extensions/14a-Language Extensions.sysml
index affcb27e2..68f0beb26 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/14-Language Extensions/14a-Language Extensions.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/14-Language Extensions/14a-Language Extensions.sysml
@@ -7,7 +7,7 @@ package '14a-Language Extensions' {
enum secret;
}
metadata def Classified {
- :>> annotatedElement : SysML::Systems::PartUsage;
+ :>> annotatedElement : SysML::PartUsage;
attribute classificationLevel: ClassificationLevel[1];
}
}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/14-Language Extensions/14c-Language Extensions.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/14-Language Extensions/14c-Language Extensions.sysml
index 0d2b88335..9acdfb2e9 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/14-Language Extensions/14c-Language Extensions.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/14-Language Extensions/14c-Language Extensions.sysml
@@ -62,42 +62,42 @@ package '14c-Language-Extensions' {
ref status: Status;
}
metadata def SituationMetadata :> SemanticMetadata {
- :>> baseType default = situations meta SysML::Systems::Usage;
+ :>> baseType default = situations meta SysML::Usage;
}
metadata def CauseMetadata :> situation {
- :>> baseType = causes meta SysML::Systems::Usage;
+ :>> baseType = causes meta SysML::Usage;
}
metadata def FailureModeMetadata :> situation {
- :>> baseType = failureModes meta SysML::Systems::Usage;
+ :>> baseType = failureModes meta SysML::Usage;
}
metadata def EffectMetadata :> situation {
- :>> baseType = effects meta SysML::Systems::Usage;
+ :>> baseType = effects meta SysML::Usage;
}
metadata def FMEAItemMetadata :> situation {
- :> annotatedElement : SysML::Systems::ItemDefinition;
- :> annotatedElement : SysML::Systems::ItemUsage;
- :>> baseType = fmeaItems meta SysML::Systems::Usage;
+ :> annotatedElement : SysML::ItemDefinition;
+ :> annotatedElement : SysML::ItemUsage;
+ :>> baseType = fmeaItems meta SysML::Usage;
}
metadata def CausationMetadata :> SemanticMetadata {
- :>> annotatedElement : SysML::Systems::ConnectionUsage;
- :>> baseType = causations meta SysML::Systems::Usage;
+ :>> annotatedElement : SysML::ConnectionUsage;
+ :>> baseType = causations meta SysML::Usage;
}
metadata def FMEARequirementMetadata :> SemanticMetadata {
- :>> annotatedElement : SysML::Systems::RequirementUsage;
- :>> baseType = fmeaRequirements meta SysML::Systems::Usage;
+ :>> annotatedElement : SysML::RequirementUsage;
+ :>> baseType = fmeaRequirements meta SysML::Usage;
}
metadata def ViolationMetadata :> SemanticMetadata {
- :>> annotatedElement : SysML::Systems::ConnectionUsage;
- :>> baseType = violations meta SysML::Systems::Usage;
+ :>> annotatedElement : SysML::ConnectionUsage;
+ :>> baseType = violations meta SysML::Usage;
}
abstract metadata def ControllingMeasureMetadata :> SemanticMetadata {
- :>> annotatedElement : SysML::Systems::ConnectionUsage;
+ :>> annotatedElement : SysML::ConnectionUsage;
}
metadata def PreventionMetadata :> ControllingMeasureMetadata {
- :>> baseType = preventions meta SysML::Systems::Usage;
+ :>> baseType = preventions meta SysML::Usage;
}
metadata def MitigationMetadata :> ControllingMeasureMetadata {
- :>> baseType = mitigations meta SysML::Systems::Usage;
+ :>> baseType = mitigations meta SysML::Usage;
}
}
package FMEAUserModel {
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_01-Constants.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_01-Constants.sysml
index c2a91542b..282003bae 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_01-Constants.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_01-Constants.sysml
@@ -35,7 +35,7 @@ package '15_01-Constants' {
* The reference source is:
* CODATA - Task Group on Fundamental Physical Constants (TGFC) - 2018 CODATA recommended values
* See https://codata.org/initiatives/strategic-programme/fundamental-physical-constants/
- * For the actual values see https://pml.nist.gov/cuu/Constants/
+ * For the actual values see https://pml.nist.gov/cuu/Constants/
*/
attribute 'fine structure constant': DimensionOneValue = 0.0072973525693[one];
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_05-Unification of Expression and Constraint Definition.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_05-Unification of Expression and Constraint Definition.sysml
index f48f52bb0..3ce8b64b3 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_05-Unification of Expression and Constraint Definition.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_05-Unification of Expression and Constraint Definition.sysml
@@ -9,7 +9,7 @@ package '15_05-Unification of Expression and Constraint Definition' {
attribute length: LengthValue[1] = 4.82[m];
part wheelAssy: WheelAssy[4];
constraint discBrakeConstraint: DiscBrakeConstraint { doc /*
- * This constraint is computed, but not asserted. This means a tool can identify
+ * This constraint is computed, but not asserted. This means a tool can identify
* when it is violated without the model being inconsistent.
*/
in wheelAssy = Vehicle_2::wheelAssy; }
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_06-System of Quantities.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_06-System of Quantities.sysml
index 622197b63..7ba18004a 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_06-System of Quantities.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_06-System of Quantities.sysml
@@ -6,17 +6,17 @@ package '15_06-System of Quantities' {
* Its structure is modeled after the International System of Quantities (ISQ):
* - Quantity dimension is defined as the product of powers of a selected set of base quantities.
* - A system of quantities is multi-dimensional space spanned by the powers of its base quantities.
- * - Any base quantity is modeled as a specialization of a SimpleUnit. Such a specialized SimpleUnit defines one base unit vector
- * (with power one by definition), e.g. MassUnit with symbol M, that establishes a base quantity dimension for the system of quantities,
+ * - Any base quantity is modeled as a specialization of a SimpleUnit. Such a specialized SimpleUnit defines one base unit vector
+ * (with power one by definition), e.g. MassUnit with symbol M, that establishes a base quantity dimension for the system of quantities,
* without committing yet to a particular choice of measurement unit.
* - To complete the system of quantities any number of derived quantities can be added.
- * - A derived quantity is modeled as a specialization of a DerivedUnit. A DerivedUnit is defined in terms of so-called UnitPowerFactors.
+ * - A derived quantity is modeled as a specialization of a DerivedUnit. A DerivedUnit is defined in terms of so-called UnitPowerFactors.
* Each UnitPowerFactor is a combination of a base (or other derived) quantity and an exponent.
- * - As an example the AccelerationUnit (specialization of DerivedUnit) can be defined as the combination of LengthUnit (symbol L)
+ * - As an example the AccelerationUnit (specialization of DerivedUnit) can be defined as the combination of LengthUnit (symbol L)
* to the power 1 and TimeUnit (symbol T) to the power -2, so having quantity dimension L¹⋅T⁻².
- * - A quantity of dimension one is defined as a derived quantity for which the effective exponent for each
+ * - A quantity of dimension one is defined as a derived quantity for which the effective exponent for each
* of its base quantity power factors is zero. Historically a quantity of dimension one was also called a dimensionless quantity.
- * - A quantity of dimension one may be defined by adding all quantity power factors that cancel out by having positive and negative
+ * - A quantity of dimension one may be defined by adding all quantity power factors that cancel out by having positive and negative
* exponents. Doing so enables distinction between different 'kinds of' quantities of dimension one, e.g:
* angle (L¹⋅L⁻¹), mass ratio (L¹⋅L⁻¹), power ratio (L²⋅M⋅T⁻³⋅L⁻²⋅M⁻¹⋅T³), Mach number (L¹⋅T⁻¹⋅L⁻¹⋅T¹).
*
@@ -27,7 +27,7 @@ package '15_06-System of Quantities' {
* Above capabilities were implemented in:
* - standard library Quantities:
* TensorQuantityValue, VectorQuantityValue, ScalarQuantityValue,
- * tensorQuantities, vectorQuantities, scalarQuantities,
+ * tensorQuantities, vectorQuantities, scalarQuantities,
* SystemOfQuantities
* - standard library MeasurementReferences:
* TensorMeasurementReference, VectorMeasurementReference, ScalarMeasurementReference,
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_07-System of Units and Scales.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_07-System of Units and Scales.sysml
index 3bcd8857d..4896b6b17 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_07-System of Units and Scales.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_07-System of Units and Scales.sysml
@@ -10,17 +10,17 @@ package '15_07-System of Units and Scales' {
* - An actual measurement unit is modeled as a usage of a specialization of either SimpleUnit or DerivedUnit, e.g. TimeUnit or ForceUnit,
* see the SI package.
* - The quantity dimension of the actual unit usage must match the quantity dimension of the generic quantity unit definition that it is a usage of.
- * - A system of units and scales must define exactly one selected base unit for each base quantity in the associated system of quantities. The collection of
+ * - A system of units and scales must define exactly one selected base unit for each base quantity in the associated system of quantities. The collection of
* base units forms the foundation for automated quantity value conversion between any pair of compatible units and/or scales.
* - If only a measurement unit is used on a quantity value, it implies expression on a ratio scale, in other words only the ratio between the actual quantity value,
- * and the defined unit value is of importance. On ratio scales for one kind of quantity that only differ in their unit (e.g. metre and inch)
+ * and the defined unit value is of importance. On ratio scales for one kind of quantity that only differ in their unit (e.g. metre and inch)
* zero is zero no matter what unit is selected.
- * - A unit may carry a conversion factor definition w.r.t. to another reference unit. It can be a conversion by convention (e.g. between metre and foot) or
- * via an ISO/IEC 80000 prefix symbol that indicates a decimal or binary multiple or sub-multiple (e.g. kilo, nano, mega, kibi, mebi, ...). See package SIPrefixes.
+ * - A unit may carry a conversion factor definition w.r.t. to another reference unit. It can be a conversion by convention (e.g. between metre and foot) or
+ * via an ISO/IEC 80000 prefix symbol that indicates a decimal or binary multiple or sub-multiple (e.g. kilo, nano, mega, kibi, mebi, ...). See package SIPrefixes.
* - In addition to measurement units / ratio scales also other types of measurement scales are supported. The additional scales are:
- * - ordinal scales (e.g. Beaufort wind force, Richter Scale, Rockwell C hardness scale),
- * - interval scales (e.g. absolute temperature in deg C or F),
- * - cyclic ratio scales (e.g. rotation angle with modulus 360 degree),
+ * - ordinal scales (e.g. Beaufort wind force, Richter Scale, Rockwell C hardness scale),
+ * - interval scales (e.g. absolute temperature in deg C or F),
+ * - cyclic ratio scales (e.g. rotation angle with modulus 360 degree),
* - logarithmic scales (e.g. dB(A) or dBA sound pressure level w.r.t. a reference ambient pressure, dB(m) or dBm power ratio w.r.t. 1 mW).
* - Any base unit quantity is modeled as a specialization of a SimpleUnit. This specialized SimpleUnit (e.g. MassUnit) defines one base unit vector (with power one by definition)
* that establishes a base quantity dimension for the system of quantities, without committing yet to a particular choice of measurement unit.
@@ -33,7 +33,7 @@ package '15_07-System of Units and Scales' {
* Above capabilities were implemented in:
* - standard library MeasurementReferences:
* TensorMeasurementReference, VectorMeasurementReference, ScalarMeasurementReference,
- * MeasurementUnit, OrdinalScale, IntervalScale, CyclicRatioScale, LogarithmicScale,
+ * MeasurementUnit, OrdinalScale, IntervalScale, CyclicRatioScale, LogarithmicScale,
* SystemOfUnits
* - standard library SI:
* attribute 'ISO/IEC 80000 International System of Units' : SystemOfUnits
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_10-Primitive Data Types.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_10-Primitive Data Types.sysml
index 9679256c6..79da45b81 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_10-Primitive Data Types.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/15-Properties-Values-Expressions/15_10-Primitive Data Types.sysml
@@ -1,7 +1,7 @@
package '15.10-Primitive Data Types' {
/*
* Primitive data types are defined in normative model libraries.
- * Any more specialized data types can be declared in user-defined
+ * Any more specialized data types can be declared in user-defined
* model libraries or models as needed.
*/
private import ScalarValues::Integer {
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/18-Use Case/18-Use Case.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/18-Use Case/18-Use Case.sysml
index 8eee106b3..9e0814247 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/18-Use Case/18-Use Case.sysml
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/18-Use Case/18-Use Case.sysml
@@ -11,9 +11,9 @@ package '18-Use Case' {
objective {
doc
/*
- * Satisfy mission requirements to transport driver and passengers
- * from starting location to ending location in conformance with
- * the driving profile and meet the mission requirements for safety,
+ * Satisfy mission requirements to transport driver and passengers
+ * from starting location to ending location in conformance with
+ * the driving profile and meet the mission requirements for safety,
* reliability, comfort, and affordability.
*/
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ActionTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ActionTest.sysml
new file mode 100644
index 000000000..b7c2227a9
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ActionTest.sysml
@@ -0,0 +1,48 @@
+package ActionTest {
+ action def A {
+ in x;
+ }
+ action a: A {
+ first start;
+ action b {
+ in y = x;
+ }
+ bind x = b.y;
+ }
+ attribute def S;
+ action a1 {
+ first start;
+ then merge m;
+ then accept S;
+ then accept sig after 10[SI::s];
+ then accept at new Time::Iso8601DateTime("2022-01-30T01:00:00Z");
+ then send new S() to b;
+ then accept when b.f;
+ then decide;
+ if true then m;
+ else done;
+ }
+ action a2 {
+ in s: S;
+ action aa {
+ out part target;
+ }
+ flow aa.target to snd.receiver;
+ snd send {
+ in :>> payload = s;
+ }
+ snd2 send via this to aa.target;
+ bind s = snd2.payload;
+ }
+ action b {
+ attribute f: ScalarValues::Boolean;
+ ref action a: A;
+ }
+ action def c {
+ first start;
+ then action c1 {
+ terminate c1;
+ }
+ then terminate;
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/AliasTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/AliasTest.sysml
new file mode 100644
index 000000000..9186123b8
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/AliasTest.sysml
@@ -0,0 +1,17 @@
+package AliasTest {
+ private import ISQSpaceTime::breadth;
+ attribute b :> breadth;
+ part def P1 {
+ port porig1;
+ alias po1 for porig1;
+ }
+ part p1: P1 {
+ port po1 :>> porig1;
+ }
+ part p2: P1 {
+ port pdest;
+ alias pd1 for pdest;
+ }
+ connect p1.po1 to p2.pdest;
+ connect p1.po1 to p2.pdest;
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/AllocationTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/AllocationTest.sysml
new file mode 100644
index 000000000..84fbce0d2
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/AllocationTest.sysml
@@ -0,0 +1,27 @@
+package AllocationTest {
+ part def Logical {
+ part component;
+ }
+ part def Physical {
+ part assembly {
+ part element;
+ }
+ }
+ part l: Logical {
+ part :>> component;
+ }
+ part p: Physical {
+ part :>> assembly {
+ part :>> element;
+ }
+ allocate l.component to assembly.element;
+ }
+ allocation def A;
+ allocation def Logical_to_Physical :> A {
+ end ref logical: Logical;
+ end ref physical: Physical;
+ }
+ allocation allocation1: Logical_to_Physical allocate l to p;
+ allocation allocation2: Logical_to_Physical allocate logical ::> l to physical ::> p;
+ allocate l.component to p.assembly.element;
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/AnalysisTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/AnalysisTest.sysml
new file mode 100644
index 000000000..915ce5208
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/AnalysisTest.sysml
@@ -0,0 +1,31 @@
+package AnalysisTest {
+ part def V {
+ ref m;
+ }
+ part vv: V;
+ requirement def AnalysisObjective {
+ doc
+ /* ... */
+ }
+ analysis def AnalysisCase {
+ subject v: V;
+ objective obj: AnalysisObjective {
+ subject = Requirements::RequirementConstraintCheck::result;
+ }
+ v.m }
+ analysis def AnalysisPlan {
+ subject v: V;
+ objective {
+ doc
+ /* ... */
+ }
+ analysis analysisCase: AnalysisCase {
+ return mass;
+ }
+ }
+ part analysisContext {
+ analysis analysisPlan: AnalysisPlan {
+ subject v = vv;
+ }
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/AssignmentTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/AssignmentTest.sysml
new file mode 100644
index 000000000..a93a0f084
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/AssignmentTest.sysml
@@ -0,0 +1,34 @@
+package AssignmentTest {
+ part def Counter {
+ attribute count: ScalarValues::Integer := 0;
+ action incr {
+ assign count := count + 1;
+ }
+ action decr {
+ assign count := count - 1;
+ }
+ }
+ attribute def Incr;
+ attribute def Decr;
+ state def Counting {
+ part counter: Counter;
+ entry assign counter.count := 0;
+ then state wait;
+ accept Incr then increment;
+ accept Decr then decrement;
+ state increment {
+ do assign counter.count := counter.count + 1;
+ }
+ then wait;
+ state decrement {
+ do assign counter.count := counter.count - 1;
+ }
+ then wait;
+ }
+ calc def Increment { in c: Counter; return : Counter; perform c.incr; c }
+ action a {
+ state counting: Counting;
+ assign counting.counter.count := counting.counter.count + 1;
+ assign counting.counter.count := Increment(counting.counter).count;
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/CalculationTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/CalculationTest.sysml
new file mode 100644
index 000000000..4decaabff
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/CalculationTest.sysml
@@ -0,0 +1,18 @@
+package CalculationExample {
+ private import ISQ::*;
+ private import NumericalFunctions::*;
+ part def VehiclePart {
+ attribute m: MassValue;
+ }
+ part def Vehicle :> VehiclePart;
+ part vehicle: Vehicle {
+ part eng: VehiclePart;
+ part trans: VehiclePart;
+ attribute ::> m = ms.totalMass;
+ }
+ calc def MassSum { in partMasses: MassValue[0..*]; return totalMass: MassValue = sum(partMasses); }
+ calc ms: MassSum { in partMasses = (vehicle.eng.m, vehicle.trans.m); return totalMass; }
+ part vehicles[*] = (vehicle, vehicle);
+ attribute masses1[*] = (vehicles as VehiclePart).m;
+ attribute masses2[*] = (vehicles as vehicle).m;
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/CommentTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/CommentTest.sysml
new file mode 100644
index 000000000..1fa79a8b0
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/CommentTest.sysml
@@ -0,0 +1,42 @@
+/* AAA */
+package CommentTest {
+ /*
+ * AAA
+ * BBB*/
+ /*
+ *
+ *
+ * AAA ***
+ * BBB
+ */
+ /*
+ * AAAA
+ * BBBB */
+ /*
+ * AAAA
+ *
+ *
+ * BBBB
+ *
+ * CCCC
+ */
+ locale "en_US" /*
+ * AAAA
+ * BBBB
+ * CCC DDD
+ */
+ /* comment inside a package */
+ doc locale "en_US"
+ /* Documentation about Package */
+ comment cmt /* Named Comment */
+ comment cmt_cmt about cmt /* Comment about Comment */
+ comment about C /* Documention Comment about Part Def */
+ part def C {
+ doc
+ /* Documentation in Part Def */
+ /* Comment in Part Def */
+ comment about CommentTest locale "en_US" /* Comment about Package */
+ }
+ /* abc */
+ part def A;
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ConjugationTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ConjugationTest.sysml
new file mode 100644
index 000000000..7d1151497
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ConjugationTest.sysml
@@ -0,0 +1,29 @@
+package ConjugationTest {
+ port def P;
+ part def B {
+ port p1: P;
+ port p2: ~P;
+ }
+ connection def A {
+ end port p1: P;
+ end port p2: ~P;
+ }
+ interface def I {
+ end p1: P;
+ end p2: ~P;
+ }
+ part def B1 {
+ part p {
+ port p1: P;
+ port p2: ~P;
+ }
+ connection a: A {
+ end port p3: P ::> p.p1;
+ end port p4: ~P ::> p.p2;
+ }
+ interface i: I {
+ end p3: P ::> p.p1;
+ end p4: ~P ::> p.p2;
+ }
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ConnectionTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ConnectionTest.sysml
new file mode 100644
index 000000000..3c2c50fc5
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ConnectionTest.sysml
@@ -0,0 +1,56 @@
+package ConnectionTest {
+ part p {
+ part x {
+ part x1;
+ }
+ }
+ part def P {
+ part y;
+ connect p to y;
+ part p1 :> p;
+ connect p1.x to y;
+ connect p1.x.x1 to y;
+ part a;
+ part b;
+ bind a = b;
+ binding ab bind a = b;
+ binding ab1: AB bind a = b;
+ first a then b;
+ succession s first a then b;
+ succession s1: AB first a then b;
+ }
+ abstract connection def C {
+ part p;
+ end ref end1;
+ end ref end2;
+ end ref end3;
+ }
+ part d1;
+ part d2;
+ part d3;
+ part d4;
+ connection bus: C connect (d1, d2, d3, d4);
+ connection : C {
+ end ref :>> end1 ::> d1;
+ end ref end2 ::> d2;
+ end ref end3 ::> d3;
+ }
+ connection {
+ part q;
+ end ref end1 ::> d1 :> q;
+ end ref end2 ::> d2;
+ }
+ abstract flow def F;
+ abstract message : F from p to p;
+ part def A {
+ ref b: B;
+ }
+ part def B;
+ connection def AB {
+ end[1] item a: A {
+ @ M;
+ }
+ end ref b: B;
+ }
+ metadata def M;
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ConstraintTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ConstraintTest.sysml
new file mode 100644
index 000000000..77aad3a04
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ConstraintTest.sysml
@@ -0,0 +1,45 @@
+package ConstraintTest {
+ private import ISQ::MassValue;
+ private import SI::kg;
+ private import NumericalFunctions::sum;
+ constraint def MassAnalysis { attribute totalMass: MassValue; attribute componentMasses: MassValue[0..*]; totalMass == sum(componentMasses) }
+ part def Component {
+ attribute mass: MassValue;
+ }
+ part vehicle: Component {
+ part engine: Component;
+ part frontAxleAssembly: Component;
+ part rearAxleAssembly: Component;
+ }
+ part vehicle1a :> vehicle {
+ assert constraint massAnalysis: MassAnalysis { attribute :>> totalMass; attribute :>> componentMasses; }
+ bind massAnalysis.totalMass = mass;
+ bind massAnalysis.componentMasses = engine.mass;
+ bind massAnalysis.componentMasses = frontAxleAssembly.mass;
+ bind massAnalysis.componentMasses = rearAxleAssembly.mass;
+ }
+ part vehicle1b :> vehicle {
+ assert constraint massAnalysis: MassAnalysis { attribute :>> totalMass = mass; attribute :>> componentMasses = (engine.mass, frontAxleAssembly.mass, rearAxleAssembly.mass); }
+ }
+ constraint def MassAnalysis2 { in totalMass: MassValue; in componentMasses: MassValue[0..*]; totalMass == sum(componentMasses) }
+ part vehicle2a :> vehicle {
+ assert constraint massConstraint: MassAnalysis2;
+ bind massConstraint.totalMass = mass;
+ bind massConstraint.componentMasses = engine.mass;
+ bind massConstraint.componentMasses = frontAxleAssembly.mass;
+ bind massConstraint.componentMasses = rearAxleAssembly.mass;
+ }
+ part vehicle2b :> vehicle {
+ assert constraint massAnalysis2: MassAnalysis2 { in totalMass = mass; in componentMasses = (engine.mass, frontAxleAssembly.mass, rearAxleAssembly.mass); }
+ }
+ constraint def MassAnalysis3 { in totalMass: MassValue; in componentMasses: MassValue[0..*]; }
+ constraint massAnalysis3: MassAnalysis3 { in totalMass: MassValue; in componentMasses: MassValue[0..*]; totalMass == sum(componentMasses) }
+ part vehicle3 :> vehicle {
+ assert massAnalysis3 { in totalMass = mass; in componentMasses = (engine.mass, frontAxleAssembly.mass, rearAxleAssembly.mass); }
+ }
+ part vehicle4 :> vehicle {
+ assert constraint { mass == (engine.mass + frontAxleAssembly.mass) + rearAxleAssembly.mass }
+ }
+ constraint massLimitation { ref mass: MassValue; ref massLimit: MassValue; mass < massLimit }
+ assert not massLimitation { :>> mass = vehicle3.mass; :>> massLimit = vehicle4.mass; }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ControlNodeTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ControlNodeTest.sysml
new file mode 100644
index 000000000..b6463b6a0
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ControlNodeTest.sysml
@@ -0,0 +1,28 @@
+action def ControlNodeTest {
+ action A1;
+ then J;
+ action A2 {
+ out a;
+ }
+ then J;
+ flow A2.a to F.a;
+ join J;
+ then fork F {
+ in a;
+ out b1;
+ out b2;
+ }
+ then B1;
+ then B2;
+ flow F.b1 to B1.b;
+ flow F.b2 to B2.b;
+ action B1 {
+ in b;
+ }
+ then M;
+ action B2 {
+ in b;
+ }
+ then M;
+ merge M;
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/DecisionTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/DecisionTest.sysml
new file mode 100644
index 000000000..bda01509a
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/DecisionTest.sysml
@@ -0,0 +1,16 @@
+action def DecisionTest {
+ attribute x = 1;
+ decide 'test x';
+ if x == 1 then A1;
+ if x > 1 then A2;
+ else A3;
+ then decide D;
+ if true then A1;
+ if false then A2;
+ action A1;
+ action A2;
+ action A3;
+ succession S first A1 if x == 0 then A2;
+ first A3;
+ if x > 0 then 'test x';
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/DefaultValueTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/DefaultValueTest.sysml
new file mode 100644
index 000000000..09232c0ed
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/DefaultValueTest.sysml
@@ -0,0 +1,13 @@
+package DefaultValueTest {
+ part def V {
+ attribute m default = 10;
+ attribute n = 20;
+ }
+ part v1: V {
+ attribute :>> m = 20;
+ }
+ part def W :> V {
+ attribute :>> m default = n;
+ }
+ part v2 = new W();
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/DependencyTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/DependencyTest.sysml
new file mode 100644
index 000000000..3253d6e96
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/DependencyTest.sysml
@@ -0,0 +1,14 @@
+package DependencyTest {
+ package System {
+ package 'Application Layer';
+ package 'Service Layer';
+ package 'Data Layer';
+ }
+ private import System::*;
+ dependency Use from 'Application Layer' to 'Service Layer';
+ dependency 'Service Layer' to 'Data Layer';
+ attribute x;
+ attribute y;
+ attribute z;
+ dependency z to x, y;
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/EnumerationTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/EnumerationTest.sysml
new file mode 100644
index 000000000..249858771
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/EnumerationTest.sysml
@@ -0,0 +1,52 @@
+package EnumerationTest {
+ attribute def Color {
+ attribute val: ScalarValues::Natural;
+ }
+ enum def ColorKind :> Color {
+ doc
+ /*
+ * An EnumerationDefinition can contain only EnumerationUsages. However,
+ * it can specialize an AttributeDefinition in order to inherit
+ * common features for its enumeration values.
+ */
+
+ enum red {
+ :>> val = 0;
+ }
+ enum blue {
+ :>> val = 1;
+ }
+ enum green {
+ :>> val = 2;
+ }
+ }
+ enum color: ColorKind;
+ enum color1 = ColorKind::blue;
+ attribute color2: ColorKind = color1;
+ enum def E1 {
+ enum a;
+ enum b;
+ enum c;
+ doc
+ /*
+ * The "enum" keyword is optional for EnumerationUsages used to define the
+ * enumerated values of an EnumerationDefinition.
+ */
+
+ }
+ enum def E2;
+ attribute def Size :> ScalarValues::Real {
+ doc
+ /*
+ * An EnumerationDefinition can also be used to restrict a supertype to
+ * specific values.
+ */
+
+ }
+ enum def SizeChoice :> Size {
+ enum = 60.0;
+ enum = 70.0;
+ enum = 80.0;
+ }
+ enum size: SizeChoice = 60.0;
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/FeaturePathTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/FeaturePathTest.sysml
new file mode 100644
index 000000000..e7c19aa50
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/FeaturePathTest.sysml
@@ -0,0 +1,32 @@
+package Q {
+ part def F {
+ part a: A;
+ }
+ part f: F;
+ part def A {
+ part g = f.a;
+ }
+ part def B {
+ part f: F;
+ part a: A;
+ }
+ part def C {
+ part b: B {
+ connect f.a to a.g;
+ bind f.a = a.g;
+ }
+ part c :> b.f {
+ part aa :> a;
+ }
+ flow b.f.a to c.aa;
+ }
+ part e1 {
+ attribute x: E;
+ bind e1.x = E::e2;
+ }
+ enum def E {
+ enum e1;
+ enum e2;
+ }
+ part g = new A().g.g.g;
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/IndividualTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/IndividualTest.sysml
new file mode 100644
index 000000000..486d4cc5e
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/IndividualTest.sysml
@@ -0,0 +1,33 @@
+package IndividualTest {
+ individual def IO1;
+ individual def IO2 {
+ individual io: IO1;
+ }
+ individual item def II1 {
+ individual item ii: II1;
+ }
+ item def I {
+ part i: I;
+ }
+ individual item def II2 :> I {
+ individual item :>> i : II2;
+ }
+ individual part def IP1 {
+ individual part p: IP1;
+ }
+ part def P {
+ part p: P;
+ }
+ individual part def IP2 :> P {
+ individual part :>> p : IP2;
+ }
+ individual action def AP1 {
+ individual action a: AP1;
+ }
+ action def A {
+ action a: A;
+ }
+ individual action def IA2 :> A {
+ individual action :>> a : IA2;
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ItemTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ItemTest.sysml
new file mode 100644
index 000000000..1c209cfda
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ItemTest.sysml
@@ -0,0 +1,17 @@
+package ItemTest {
+ item f: A;
+ item def A {
+ item b: B;
+ protected ref part c: C;
+ }
+ abstract item def B {
+ abstract part a: A;
+ }
+ private part def C {
+ private in y: A, B;
+ }
+ port def P {
+ in item a1: A;
+ out item a2: A;
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/MultiplicityTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/MultiplicityTest.sysml
new file mode 100644
index 000000000..4310363ba
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/MultiplicityTest.sysml
@@ -0,0 +1,15 @@
+package MultiplicityTest {
+ part def P;
+ attribute n: ScalarValues::Integer = 5;
+ part a[1];
+ part b[0..2] : P;
+ part c: P[2..*];
+ part d[*];
+ part e[n];
+ part f[n..*];
+ part g[1..n];
+ attribute def A {
+ attribute i: ScalarValues::Integer;
+ attribute x: A[i];
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/OccurrenceTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/OccurrenceTest.sysml
new file mode 100644
index 000000000..448e7081d
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/OccurrenceTest.sysml
@@ -0,0 +1,28 @@
+package OccurrenceTest {
+ occurrence def Occ {
+ attribute a;
+ ref occurrence occ1: Occ;
+ occurrence occ2: Occ;
+ item x;
+ part y;
+ individual snapshot s: Ind;
+ timeslice t;
+ }
+ occurrence occ: Occ {
+ occurrence o1: Occ;
+ ref occurrence o2: Occ;
+ item z;
+ }
+ individual def Ind {
+ snapshot s2;
+ timeslice t2;
+ }
+ individual ind: Ind, Occ {
+ snapshot s3;
+ individual timeslice t3 :> ind;
+ individual snapshot s4: Ind;
+ }
+ occurrence o1 {
+ occurrence o2;
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ParameterTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ParameterTest.sysml
new file mode 100644
index 000000000..94fd0da24
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ParameterTest.sysml
@@ -0,0 +1,12 @@
+package ParameterTest {
+ attribute def A {
+ attribute x: ScalarValues::String;
+ attribute y: A;
+ }
+ attribute a: A;
+ calc def F { in p: A; in q: ScalarValues::Integer; return : ScalarValues::Integer; }
+ attribute f = F(a, 2);
+ attribute g = F(q = 1, p = a);
+ attribute b = new A(y = a, x = "");
+ attribute c = new A("test2");
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/PartTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/PartTest.sysml
new file mode 100644
index 000000000..81f314bf0
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/PartTest.sysml
@@ -0,0 +1,44 @@
+package PartTest {
+ part f: A;
+ part def A {
+ part <'1'> b: xx;
+ protected port c: C;
+ constant attribute x[0..2];
+ derived constant attribute y :> x;
+ ref z: ScalarValues::Integer;
+ }
+ item def S;
+ abstract part def B {
+ abstract part a: A[1..2];
+ abstract part b :> a;
+ abstract part c[0..1] :> a;
+ port x: ~C {
+ port p;
+ ref port q;
+ }
+ package P;
+ succession flow x.p to a1.aa.receiver;
+ action a1 {
+ accept S via x;
+ action aa accept S;
+ }
+ perform action a2;
+ state s1;
+ exhibit state s2;
+ }
+ private port def C {
+ private in y: A, xx {
+ part B_b :>> xx::b;
+ part B_c :>> xx::c;
+ port B_x :>> xx::x;
+ }
+ alias z1 for y;
+ alias z2 for y;
+ port c1: C;
+ ref port c2: C;
+ }
+ part p1 :> p2;
+ part p2 :> p3;
+ part p3 :> p1;
+ part p4 :> p4;
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/RequirementTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/RequirementTest.sysml
new file mode 100644
index 000000000..4f446b2f0
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/RequirementTest.sysml
@@ -0,0 +1,29 @@
+package RequirementTest {
+ constraint def C;
+ constraint c: C;
+ private import q::**;
+ requirement def R {
+ assume constraint c1: C;
+ require c;
+ doc
+ /* */
+ requirement;
+ requirement def <'1'> A {
+ doc
+ /* Text */
+ subject s;
+ }
+ }
+ requirement def R1 {
+ require constraint c1 :>> c;
+ }
+ part p;
+ part q {
+ requirement r: R;
+ assert satisfy r by p;
+ assert satisfy r by q;
+ }
+ requirement r1: R1;
+ assert not satisfy r1 by p;
+ assert not satisfy r1 by q;
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/RootPackageTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/RootPackageTest.sysml
new file mode 100644
index 000000000..26875c4da
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/RootPackageTest.sysml
@@ -0,0 +1,11 @@
+package P1 {
+ part def A;
+}
+package P2 {
+ private import P1::*;
+ part a: A;
+}
+private import P2::*;
+package P3 {
+ part b :> a;
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/StateTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/StateTest.sysml
new file mode 100644
index 000000000..5d93c1613
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/StateTest.sysml
@@ -0,0 +1,47 @@
+package StateTest {
+ attribute def Sig {
+ ref x;
+ }
+ attribute def Exit;
+ part p;
+ action act;
+ state def S {
+ do action A;
+ entry;
+ then S1;
+ state S1;
+ accept s: Sig do action D then S2;
+ state S2 {
+ do send new Sig(T.s.x) to p;
+ state S3;
+ }
+ accept Exit then States::StateAction::done;
+ transition S1 accept s: Sig do action D then S2.S3;
+ transition T first S2.S3 accept s: Sig via p if true do send s to p then S1;
+ exit act;
+ state S3 {
+ state S3a;
+ }
+ transition S3.S3a then S1;
+ }
+ state s0 {
+ state s1 {
+ state s2;
+ }
+ state s3 {
+ state s4;
+ }
+ transition t1 first s1.s2 then s3.s4;
+ }
+ state s parallel {
+ state s1;
+ state s2;
+ }
+ state s4 {
+ do action a;
+ action c;
+ }
+ state s5 :> s4 {
+ do action b :>> c;
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/StructuredControlTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/StructuredControlTest.sysml
new file mode 100644
index 000000000..2b6ebe155
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/StructuredControlTest.sysml
@@ -0,0 +1,32 @@
+package StructuredControlTest {
+ action {
+ attribute i: ScalarValues::Integer := 0;
+ attribute b: ScalarValues::Boolean;
+ if i < 0 {
+ assign i := 0;
+ }
+ else in if i == 0 {
+ assign i := 1;
+ }
+ else {
+ assign i := i + 1;
+ }
+ if i > 0 {
+ assign i := i + 1;
+ }
+ then action aLoop while i > 0 {
+ assign i := i - 1;
+ }
+ until b;
+ then while i > 0 {
+ assign i := i - 1;
+ }
+ loop {
+ assign i := i - 1;
+ }
+ until b;
+ for n: ScalarValues::Integer in (1, 2, 3) {
+ assign i := i * n;
+ }
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/TextualRepresentationTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/TextualRepresentationTest.sysml
new file mode 100644
index 000000000..e3291ff22
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/TextualRepresentationTest.sysml
@@ -0,0 +1,15 @@
+package TextualRepresentationTest {
+ private import ScalarValues::Real;
+ item def C {
+ attribute x: Real;
+ assert constraint x_constraint { rep inOCL language "ocl"/* self.x > 0.0 */ }
+ }
+ action def setX {
+ in c: C;
+ in newX: Real;
+ language "alf"/*
+ * c.x = newX;
+ * WriteLine("Set new x");
+ */
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/TradeStudyTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/TradeStudyTest.sysml
new file mode 100644
index 000000000..a82cf1a32
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/TradeStudyTest.sysml
@@ -0,0 +1,13 @@
+package TradeStudyTest {
+ private import ScalarValues::Real;
+ private import TradeStudies::*;
+ part def Engine;
+ part engine1: Engine;
+ part engine2: Engine;
+ analysis engineTradeStudy: TradeStudy {
+ subject : Engine[1..*] = (engine1, engine2);
+ objective : MaximizeObjective;
+ calc :>> evaluationFunction { in part : Engine; return : Real; }
+ return part : Engine;
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/UseCaseTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/UseCaseTest.sysml
new file mode 100644
index 000000000..3f695a5ea
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/UseCaseTest.sysml
@@ -0,0 +1,32 @@
+package UseCaseTest {
+ part def System;
+ part def User;
+ use case def UseSystem {
+ subject system: System;
+ actor user: User;
+ objective {
+ /* Goal */
+ }
+ include use case uc1: UC1;
+ include use case uc2 {
+ subject = system;
+ actor user = UseSystem::user;
+ }
+ }
+ use case def UC1;
+ part user: User;
+ use case uc2 {
+ subject;
+ actor :>> user;
+ }
+ use case u: UseSystem;
+ part system: System {
+ include uc2;
+ perform u;
+ use case uc1: UC1;
+ }
+ use case uc3 {
+ include u;
+ include system.uc1;
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/VariabilityTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/VariabilityTest.sysml
new file mode 100644
index 000000000..bb2e294f5
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/VariabilityTest.sysml
@@ -0,0 +1,32 @@
+package VariabilityTest {
+ part def P {
+ attribute a;
+ }
+ part def Q :> P;
+ attribute def B;
+ variation part def V :> P {
+ variant part x: Q {
+ attribute b: B :>> a;
+ }
+ }
+ part q: Q;
+ variation part v: P {
+ variant q {
+ attribute b: B :>> a;
+ }
+ }
+ part y: P = v::q;
+ variation action def A {
+ variant action a1;
+ variant action a2;
+ }
+ variation use case uc1 {
+ variant use case uc11;
+ variant use case uc12;
+ }
+ variation analysis a1;
+ variation verification v1;
+ variation requirement r {
+ variant requirement r1;
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/VerificationTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/VerificationTest.sysml
new file mode 100644
index 000000000..31c9b02ac
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/VerificationTest.sysml
@@ -0,0 +1,29 @@
+package VerificationTest {
+ part def V {
+ ref m: ScalarValues::Integer;
+ }
+ part vv: V;
+ requirement def R {
+ doc
+ /* ... */
+ }
+ requirement r: R;
+ verification def VerificationCase {
+ subject v: V;
+ objective {
+ verify requirement : R;
+ }
+ VerificationCases::PassIf(v.m == 0) }
+ verification def VerificationPlan {
+ subject v: V;
+ objective {
+ verify r;
+ }
+ verification verificationCase: VerificationCase;
+ }
+ part verificationContext {
+ verification verificationPlan: VerificationPlan {
+ subject v = vv;
+ }
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ViewTest.sysml b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ViewTest.sysml
new file mode 100644
index 000000000..f49edfc1b
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/Simple Tests/ViewTest.sysml
@@ -0,0 +1,34 @@
+package ViewTest {
+ package P {
+ part p1;
+ private part p2;
+ }
+ part def S;
+ concern def C {
+ subject;
+ stakeholder s: S;
+ }
+ concern c: C {
+ subject;
+ stakeholder s1;
+ }
+ viewpoint def VP {
+ frame c;
+ }
+ rendering def R;
+ rendering r: R;
+ view def V {
+ viewpoint vp: VP {
+ frame concern c1;
+ concern c2;
+ }
+ render rendering r1: R[0..1];
+ view v: V[0..*] {
+ expose P::*;
+ render r;
+ rendering r2;
+ alias vp1 for p1;
+ alias vp2 for p2;
+ }
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ActionTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ActionTest.sysmlx
new file mode 100644
index 000000000..c6b13f152
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ActionTest.sysmlx
@@ -0,0 +1,666 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/AliasTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/AliasTest.sysmlx
new file mode 100644
index 000000000..fcc095b21
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/AliasTest.sysmlx
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/AllocationTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/AllocationTest.sysmlx
new file mode 100644
index 000000000..608ad0594
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/AllocationTest.sysmlx
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/AnalysisTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/AnalysisTest.sysmlx
new file mode 100644
index 000000000..62112d9f4
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/AnalysisTest.sysmlx
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/AssignmentTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/AssignmentTest.sysmlx
new file mode 100644
index 000000000..5796a4270
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/AssignmentTest.sysmlx
@@ -0,0 +1,687 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/CalculationTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/CalculationTest.sysmlx
new file mode 100644
index 000000000..7d3d73f33
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/CalculationTest.sysmlx
@@ -0,0 +1,350 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/CommentTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/CommentTest.sysmlx
new file mode 100644
index 000000000..6c191b336
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/CommentTest.sysmlx
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ConjugationTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ConjugationTest.sysmlx
new file mode 100644
index 000000000..eebafb8f5
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ConjugationTest.sysmlx
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ConnectionTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ConnectionTest.sysmlx
new file mode 100644
index 000000000..3f53ddae9
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ConnectionTest.sysmlx
@@ -0,0 +1,459 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ConstraintTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ConstraintTest.sysmlx
new file mode 100644
index 000000000..e5c0fc78c
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ConstraintTest.sysmlx
@@ -0,0 +1,1188 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ControlNodeTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ControlNodeTest.sysmlx
new file mode 100644
index 000000000..27eba54c7
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ControlNodeTest.sysmlx
@@ -0,0 +1,254 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/DecisionTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/DecisionTest.sysmlx
new file mode 100644
index 000000000..97b3a4233
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/DecisionTest.sysmlx
@@ -0,0 +1,371 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/DefaultValueTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/DefaultValueTest.sysmlx
new file mode 100644
index 000000000..448287d57
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/DefaultValueTest.sysmlx
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/DependencyTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/DependencyTest.sysmlx
new file mode 100644
index 000000000..523625a66
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/DependencyTest.sysmlx
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/EnumerationTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/EnumerationTest.sysmlx
new file mode 100644
index 000000000..a0be2d43c
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/EnumerationTest.sysmlx
@@ -0,0 +1,158 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/FeaturePathTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/FeaturePathTest.sysmlx
new file mode 100644
index 000000000..92553ffe6
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/FeaturePathTest.sysmlx
@@ -0,0 +1,274 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/IndividualTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/IndividualTest.sysmlx
new file mode 100644
index 000000000..d3b0c0ef5
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/IndividualTest.sysmlx
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ItemTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ItemTest.sysmlx
new file mode 100644
index 000000000..4742adc74
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ItemTest.sysmlx
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/MultiplicityTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/MultiplicityTest.sysmlx
new file mode 100644
index 000000000..323fd1a56
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/MultiplicityTest.sysmlx
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/OccurrenceTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/OccurrenceTest.sysmlx
new file mode 100644
index 000000000..0adeb5879
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/OccurrenceTest.sysmlx
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ParameterTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ParameterTest.sysmlx
new file mode 100644
index 000000000..909ab4865
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ParameterTest.sysmlx
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/PartTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/PartTest.sysmlx
new file mode 100644
index 000000000..1b86bfc38
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/PartTest.sysmlx
@@ -0,0 +1,267 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/RequirementTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/RequirementTest.sysmlx
new file mode 100644
index 000000000..0c0def70d
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/RequirementTest.sysmlx
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/RootPackageTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/RootPackageTest.sysmlx
new file mode 100644
index 000000000..26d813f57
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/RootPackageTest.sysmlx
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/StateTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/StateTest.sysmlx
new file mode 100644
index 000000000..286ac22e9
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/StateTest.sysmlx
@@ -0,0 +1,493 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/StructuredControlTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/StructuredControlTest.sysmlx
new file mode 100644
index 000000000..c7d4196b1
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/StructuredControlTest.sysmlx
@@ -0,0 +1,656 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/TextualRepresentationTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/TextualRepresentationTest.sysmlx
new file mode 100644
index 000000000..12df4035f
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/TextualRepresentationTest.sysmlx
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/TradeStudyTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/TradeStudyTest.sysmlx
new file mode 100644
index 000000000..ec353cf63
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/TradeStudyTest.sysmlx
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/UseCaseTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/UseCaseTest.sysmlx
new file mode 100644
index 000000000..1dc7a10c8
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/UseCaseTest.sysmlx
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/VariabilityTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/VariabilityTest.sysmlx
new file mode 100644
index 000000000..ce9c98b83
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/VariabilityTest.sysmlx
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/VerificationTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/VerificationTest.sysmlx
new file mode 100644
index 000000000..22f631de2
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/VerificationTest.sysmlx
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ViewTest.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ViewTest.sysmlx
new file mode 100644
index 000000000..4444d39ed
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/Simple Tests/ViewTest.sysmlx
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Writers/CollectionCursorTestFixture.cs b/SysML2.NET.Serializer.TextualNotation.Tests/Writers/CollectionCursorTestFixture.cs
index b74ee6a0a..c82c38ae7 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Writers/CollectionCursorTestFixture.cs
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Writers/CollectionCursorTestFixture.cs
@@ -103,5 +103,117 @@ public void VerifyAssertAdvancedSince()
Assert.That(() => cursor.AssertAdvancedSince(positionAtEnd, "CaseBodyItem"), Throws.TypeOf());
}
+
+ ///
+ /// locates an element by role: a match at the
+ /// cursor advances it like , a match ahead is consumed out of
+ /// order and skipped by all later traversal, and the cursor otherwise stays put.
+ ///
+ [Test]
+ public void VerifyTryTake()
+ {
+ var emptyCursor = new CollectionCursor([]);
+
+ Assert.That(() => emptyCursor.TryTake(null, out _), Throws.TypeOf());
+ Assert.That(emptyCursor.TryTake(element => true, out _), Is.False);
+
+ var cursor = new CollectionCursor(["alpha", "beta", "gamma", "delta"]);
+
+ // No match leaves the cursor untouched and yields no value.
+ var missed = cursor.TryTake(element => element == "omega", out var missedValue);
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(missed, Is.False);
+ Assert.That(missedValue, Is.Null);
+ Assert.That(cursor.Position, Is.EqualTo(0));
+ }
+
+ // A match AT the cursor consumes it exactly like Move(1) — real forward progress.
+ var positionBeforeTakeAtCursor = cursor.Position;
+ var takenAtCursor = cursor.TryTake(element => element == "alpha", out var valueAtCursor);
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(takenAtCursor, Is.True);
+ Assert.That(valueAtCursor, Is.EqualTo("alpha"));
+ Assert.That(cursor.Position, Is.EqualTo(1));
+ Assert.That(cursor.Current, Is.EqualTo("beta"));
+ }
+
+ Assert.That(() => cursor.AssertAdvancedSince(positionBeforeTakeAtCursor, "TransitionUsage"), Throws.Nothing);
+
+ // A match AHEAD of the cursor is taken without advancing it; the consumed slot disappears
+ // from Current / GetNext / Move traversal.
+ var takenAhead = cursor.TryTake(element => element == "gamma", out var valueAhead);
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(takenAhead, Is.True);
+ Assert.That(valueAhead, Is.EqualTo("gamma"));
+ Assert.That(cursor.Position, Is.EqualTo(1));
+ Assert.That(cursor.Current, Is.EqualTo("beta"));
+ Assert.That(cursor.GetNext(1), Is.EqualTo("delta"));
+ }
+
+ cursor.Move();
+
+ using (Assert.EnterMultipleScope())
+ {
+ Assert.That(cursor.Current, Is.EqualTo("delta"));
+ Assert.That(cursor.Position, Is.EqualTo(3));
+ }
+
+ // A taken element can never be taken again.
+ Assert.That(cursor.TryTake(element => element == "gamma", out _), Is.False);
+
+ // The type test participates in the match: a TDerived mismatch is not taken.
+ var mixedCursor = new CollectionCursor