diff --git a/.github/workflows/CodeQuality.yml b/.github/workflows/CodeQuality.yml
index 20adafe3..7096aded 100644
--- a/.github/workflows/CodeQuality.yml
+++ b/.github/workflows/CodeQuality.yml
@@ -46,7 +46,7 @@ jobs:
run: dotnet build --no-restore --no-incremental /p:ContinuousIntegrationBuild=true
- name: Run Tests and Compute Coverage
- run: dotnet-coverage collect "dotnet test SysML2.NET.sln --no-restore --no-build --verbosity normal" -f xml -o "coverage.xml"
+ run: dotnet-coverage collect "dotnet test SysML2.NET.sln --no-restore --no-build --verbosity normal --filter TestCategory!=Integration" -f xml -o "coverage.xml"
- name: Sonarqube end
env:
diff --git a/SySML2.NET.REST.Tests/RestClientTestFixture.cs b/SySML2.NET.REST.Tests/RestClientTestFixture.cs
index 59b80937..6f912d9a 100644
--- a/SySML2.NET.REST.Tests/RestClientTestFixture.cs
+++ b/SySML2.NET.REST.Tests/RestClientTestFixture.cs
@@ -30,9 +30,10 @@ namespace SySML2.NET.REST.Tests
using NUnit.Framework;
///
- /// Suite of tests for the class
+ /// Suite of tests for the class.
///
[TestFixture]
+ [Category("Integration")]
public class RestClientTestFixture
{
private string baseUri;
diff --git a/SysML2.NET.CodeGenerator/GRAMMAR.md b/SysML2.NET.CodeGenerator/GRAMMAR.md
index e75ca2cf..dbaf9e07 100644
--- a/SysML2.NET.CodeGenerator/GRAMMAR.md
+++ b/SysML2.NET.CodeGenerator/GRAMMAR.md
@@ -176,3 +176,96 @@ dotnet test SysML2.NET.sln
```bash
grep -r "HandCoded" SysML2.NET.Serializer.TextualNotation/Writers/AutoGenTextualNotationBuilder/*.cs | wc -l
```
+
+## Known KEBNF / specification divergences
+
+The `.kebnf` files are OMG-owned and **never edited**. Where a production cannot describe the
+notation the pilot implementation actually reads and writes, the writer deviates and the deviation is
+recorded here. All of these are reported upstream in
+[SysML-v2-Release issue #124](https://github.com/Systems-Modeling/SysML-v2-Release/issues/124)
+("Several textual KEBNF productions appear unreachable or inconsistent with release examples").
+
+Do **not** "fix" the writer back to the literal production without checking this table first.
+
+### #124 item 8 — `EntryTransitionMember` emits a duplicated `then` (deviation implemented)
+
+```
+EntryTransitionMember : FeatureMembership =
+ MemberPrefix ( ownedRelatedElement += GuardedTargetSuccession
+ | 'then' ownedRelatedElement += TargetSuccession ) ';'
+
+TargetSuccession : SuccessionAsUsage =
+ ownedRelationship += SourceEndMember 'then' ownedRelationship += ConnectorEndMember
+```
+
+`TargetSuccession` supplies its own `'then'`, so the literal reading of the second alternative is
+`then then S1;`. The corpus writes `entry; then off;`
+(`Validation/05-State-based Behavior/5-State-based Behavior-2.sysml`), and the pilot's Xtext uses
+`TransitionSuccession` (`EmptySourceEndMember ConnectorEndMember`, no `'then'`) at
+`org.omg.sysml.xtext/src/org/omg/sysml/xtext/SysML.xtext:1798`.
+
+Confirmed against both independent sources of the grammar — the `.kebnf` and the OMG specification
+(SysML 2.0 §8.2.2.18.1 State Definitions, §8.2.2.17.8 Action Successions) — so this is a genuine
+specification defect, not a transcription slip.
+
+The model cannot discriminate the two productions: both are a `SuccessionAsUsage` with two
+`EndFeatureMembership`s, and the multiplicity present on the source end is added by the pilot's
+transform to *both* ends. **Deviation:** `BuildEntryTransitionMemberHandCoded` suppresses the rule's
+own `'then'` and lets `TargetSuccession` supply it. The structure the KEBNF specifies is still
+honoured; only the redundant keyword is dropped.
+
+### #124 item 7 — `end` prefix on non-reference usages (deviation accepted, not implemented)
+
+`DefaultReferenceUsage` has no `EndUsagePrefix`, so `end ref hitch` / `end port p1: P;` are
+unreachable, yet appear in the corpus (`03-Function-based Behavior/3c-…-1`, `3c-…-2`). The writer
+emits the pilot's form; the difference is recorded as an accepted deviation for those files.
+
+### Items expected to affect folders not yet validated
+
+Not yet investigated — listed so the cause is recognised on first encounter rather than
+re-diagnosed:
+
+| item | production | folder likely affected |
+|---|---|---|
+| #1 | `AllocationDefinition` missing from `DefinitionElement` | 12-Dependency Relationships |
+| #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 |
+
+Items #2, #4, #5, #6 concern productions with no corpus coverage.
+
+## Model ↔ notation reconciliations (NOT divergences)
+
+Cases where the grammar offers two conformant productions for one model, so the writer must choose.
+Nothing here deviates from the specification — unlike the divergences above.
+
+### `TargetTransitionUsage` — the implied transition source
+
+```
+StateBodyItem : Type = …
+ | ( ownedRelationship += SourceSuccessionMember )?
+ ownedRelationship += BehaviorUsageMember
+ ( ownedRelationship += TargetTransitionUsageMember )* ← shorthand
+ | ownedRelationship += TransitionUsageMember ← explicit
+```
+
+`state off; accept X then Y;` and `transition off accept X then Y;` are **both normative** and produce
+the *same* model: the pilot resolves the shorthand at parse time and stores the source explicitly as a
+`FeatureChainMember` (a non-owning `Membership` cross-referencing the state). The shorthand-ness is
+therefore not recoverable, and `TargetTransitionUsage` has **no notation for the source at all**.
+
+The writer prefers the shorthand (matching the corpus) only when all three hold, each required for
+correctness rather than style:
+
+- the transition is **anonymous** — `TargetTransitionUsage` has no `UsageDeclaration` slot, so a named
+ transition would silently lose its name (this is what keeps `5-…-1` / `5-…-1a` on the explicit form);
+- its source **is** the anchor feature of the preceding `BehaviorUsageMember` — otherwise the shorthand
+ re-parses against that state and denotes a different element;
+- it is positioned in the `( … )*` run following that member.
+
+Consequence, in `TypeTextualNotationBuilder.EmitTargetTransitionRun`: the transition's own
+`ownedRelationship` cursor is advanced once **past the source with no emission**. That is a deliberate
+exception to the `Move()` ↔ `+=` Golden Rule, valid because the elected production has no notation for
+that element. It is conditional — it only runs after `QueryImpliedSourceTransition` has confirmed
+position 0 is the source membership — so it cannot consume a real element.
diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs
index f90aaf35..89f829b7 100644
--- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs
+++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.CollectionProcessing.cs
@@ -134,6 +134,17 @@ 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.
+ if (IsGuardedBodyItemRule(nonTerminalElement.Name))
+ {
+ var guardVariableName = $"{targetProperty.Name.LowerCaseFirstLetter()}BodyItem";
+
+ whileCondition = $"{cursorVariableName}.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship {guardVariableName} && {guardVariableName}.IsValidFor{nonTerminalElement.Name}(writerContext)";
+ }
+
if (perItemCall != null)
{
writer.WriteSafeString($"while ({whileCondition}){Environment.NewLine}");
diff --git a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs
index 5a61fe88..59b6654a 100644
--- a/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs
+++ b/SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs
@@ -30,6 +30,7 @@ namespace SysML2.NET.CodeGenerator.HandleBarHelpers
using SysML2.NET.CodeGenerator.Grammar.Model;
using uml4net.Extensions;
+ using uml4net.SimpleClassifiers;
using uml4net.StructuredClassifiers;
///
@@ -266,7 +267,51 @@ private static string ResolveAssignmentTargetTypeName(AssignmentElement assignme
}
var targetClass = RuleQueryUtilities.FindClass(umlClass.Cache, typeTarget);
- return targetClass?.QueryFullyQualifiedTypeName();
+ var targetTypeName = targetClass?.QueryFullyQualifiedTypeName();
+
+ if (targetTypeName == null)
+ {
+ return null;
+ }
+
+ // A rule may PIN a property to a constant through a non-parsing assignment, e.g.
+ // `GuardExpressionMember : TransitionFeatureMembership = 'if' { kind = 'guard' } …`.
+ // Sibling rules then share one target type and are distinguishable ONLY by that constant,
+ // so it has to be part of the guard or the first sibling swallows them all.
+ var pinnedConstantPattern = ResolvePinnedConstantPattern(referencedRule, targetClass);
+
+ return pinnedConstantPattern == null ? targetTypeName : $"{targetTypeName} {pinnedConstantPattern}";
+ }
+
+ ///
+ /// Builds a C# property pattern for a constant a rule pins via a non-parsing assignment
+ /// ({ kind = 'guard' }), used to tell apart sibling rules that share a target type.
+ ///
+ /// The rule whose pinned constant is sought.
+ /// The rule's target .
+ /// The property pattern, or when nothing enum-typed is pinned.
+ private static string ResolvePinnedConstantPattern(TextualNotationRule referencedRule, IClass targetClass)
+ {
+ var pinnedAssignment = referencedRule.Alternatives
+ .SelectMany(alternative => alternative.Elements)
+ .OfType()
+ .FirstOrDefault(assignment => assignment.Operator == "=" && !string.IsNullOrWhiteSpace(assignment.Value));
+
+ if (pinnedAssignment == null)
+ {
+ return null;
+ }
+
+ var property = targetClass.QueryAllProperties()
+ .FirstOrDefault(x => string.Equals(x.Name, pinnedAssignment.PropertyName, StringComparison.OrdinalIgnoreCase));
+
+ if (property?.Type is not IEnumeration)
+ {
+ return null;
+ }
+
+ var literalName = pinnedAssignment.Value.Trim('\'').CapitalizeFirstLetter();
+ return $"{{ {property.Name.CapitalizeFirstLetter()}: {property.Type.QueryFullyQualifiedTypeName()}.{literalName} }}";
}
///
@@ -447,7 +492,13 @@ private void ProcessSingleAlternative(EncodedTextWriter writer, IClass umlClass,
}
}
- if (inlineConditionParts.Count > 0)
+ var optionalCollectionCondition = TryResolveOptionalCollectionGroupCondition(umlClass, elements, ruleGenerationContext);
+
+ if (optionalCollectionCondition != null)
+ {
+ writer.WriteSafeString($"{Environment.NewLine}if ({optionalCollectionCondition}){Environment.NewLine}");
+ }
+ else if (inlineConditionParts.Count > 0)
{
writer.WriteSafeString($"{Environment.NewLine}if ({string.Join(" || ", inlineConditionParts)}){Environment.NewLine}");
}
@@ -474,6 +525,54 @@ 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.
+ ///
+ /// The related
+ /// The optional group's elements
+ /// The current
+ /// The cursor-based condition, or when the group is not that shape.
+ private static string TryResolveOptionalCollectionGroupCondition(IClass umlClass, List elements, RuleGenerationContext ruleGenerationContext)
+ {
+ var nonTerminals = elements.OfType().ToList();
+
+ if (nonTerminals.Count != 1 || !nonTerminals[0].IsCollection || elements.Any(element => element is AssignmentElement or GroupElement))
+ {
+ return null;
+ }
+
+ var referencedRule = ruleGenerationContext.FindRule(nonTerminals[0].Name);
+ var collectionPropertyNames = referencedRule?.QueryCollectionPropertyNames(ruleGenerationContext.AllRules);
+
+ if (collectionPropertyNames?.Count != 1)
+ {
+ return null;
+ }
+
+ var targetProperty = umlClass.QueryAllProperties().SingleOrDefault(x => string.Equals(x.Name, collectionPropertyNames.Single(), StringComparison.OrdinalIgnoreCase));
+
+ if (targetProperty == null || !targetProperty.QueryIsEnumerable())
+ {
+ 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)
+ {
+ return null;
+ }
+
+ return IsGuardedBodyItemRule(nonTerminals[0].Name)
+ ? $"{existingCursor.CursorVariableName}.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship optionalBodyCandidate && optionalBodyCandidate.IsValidFor{nonTerminals[0].Name}(writerContext)"
+ : $"{existingCursor.CursorVariableName}.Current != null";
+ }
+
///
/// Processes multiple alternatives where every alternative has exactly one element.
/// Handles multi-collection assignments, unityped dispatch, and mixed-type element handling.
@@ -1414,7 +1513,8 @@ private void EmitTerminalVsBodyWithSingleNonTerminal(EncodedTextWriter writer, I
private static bool IsGuardedBodyItemRule(string bodyItemRuleName)
{
return string.Equals(bodyItemRuleName, "DefinitionBodyItem", StringComparison.Ordinal)
- || string.Equals(bodyItemRuleName, "InterfaceBodyItem", StringComparison.Ordinal);
+ || string.Equals(bodyItemRuleName, "InterfaceBodyItem", StringComparison.Ordinal)
+ || string.Equals(bodyItemRuleName, "ActionBodyItem", StringComparison.Ordinal);
}
}
}
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 836a581b..4acd7af5 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
@@ -43,6 +43,7 @@ package '1a-Parts Tree' {
/*
* 'frontAxleAssembly' is a nested part of part 'vehicle1'.
* It is a composite part of the containing part.
+ *
* (And similarly for 'rearAxleAssembly'.)
*/
part frontAxle: Axle;
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 41756e72..54ca2d4e 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
@@ -131,6 +131,7 @@ package '2a-Parts Interconnection' {
/*
* The two rear wheels of 'rearAxleAssembly' must be given
* their own names in order to be referenced in connections.
+ *
* (":>" is a shorthand here for "subsets".)
*/
part leftWheel :> rearWheel = rearWheel#(1) {
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
new file mode 100644
index 00000000..9b649bd0
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-1.sysml
@@ -0,0 +1,155 @@
+package '5-State-based Behavior-1' {
+ private import ScalarValues::*;
+ private import ISQ::*;
+ private import '3a-Function-based Behavior-1'::*;
+ package Definitions {
+ part def VehicleA {
+ /*
+ * The following declare that 'VehicleA' performs a
+ * 'provide power' action and exhibits some 'vehicle states',
+ * without giving details about these behaviors.
+ */
+ perform action 'provide power': 'Provide Power';
+ exhibit state 'vehicle states': 'Vehicle States';
+ }
+ part def VehicleController {
+ exhibit state 'controller states': 'Controller States';
+ }
+ /*
+ * Black box specifications for state definitions may also have
+ * input and output parameters, like activities, though none
+ * are used here.
+ */
+ state def 'Vehicle States';
+ state def 'Controller States';
+ action def 'Perform Self Test';
+ action def 'Apply Parking Brake';
+ action def 'Sense Temperature' {
+ out temp: ThermodynamicTemperatureValue;
+ }
+ attribute def 'Vehicle Start Signal';
+ attribute def 'Vehicle On Signal';
+ attribute def 'Vehicle Off Signal';
+ attribute def 'Start Signal';
+ attribute def 'Off Signal';
+ attribute def 'Over Temp';
+ attribute def 'Return to Normal';
+ }
+ package Usages {
+ private import Definitions::*;
+ /*
+ * These actions are used enabled in the state usage
+ * 'vehicle states', in addition to 'provide power'.
+ */
+ action 'perform self test': 'Perform Self Test';
+ action 'apply parking brake': 'Apply Parking Brake';
+ action 'sense temperature': 'Sense Temperature';
+ state 'vehicle states': 'Vehicle States' parallel {
+ /*
+ * This is a usage of the state definition 'Vehicle States'.
+ * Note that it depends specifically on on the part 'vehicle1_c1'.
+ */
+ ref vehicle: VehicleA;
+ state 'operational states' {
+ doc
+ /*
+ * The state definition for this usage is implicit.
+ */
+
+ entry action initial {
+ doc
+ /*
+ * This empty entry action acts like a start pseudo state.
+ */
+
+ }
+ transition initial then off;
+ state off;
+ transition 'off-starting' first off accept 'Vehicle Start Signal' if vehicle1_c1.'brake pedal depressed' do send new 'Start Signal'() to vehicle1_c1.vehicleController then starting {
+ /*
+ * The transition definition for a transition usage is always implicit.
+ * "accept" marks the trigger, "if" the guard and "do" the effect.
+ *
+ * The notation "new 'Start Signal'()" constructs a specific instance of the
+ * 'Start Signal' attribute def to be sent to the 'vehicleController'. If the
+ * attribute def had properties, their values would be given as arguments
+ * inside the parentheses.
+ */
+ }
+ state starting;
+ transition 'starting-on' first starting accept 'Vehicle On Signal' then on;
+ state on {
+ /*
+ * A state may have a "entry" action that is performed on entry into
+ * the state, a "do" action that is performed while in the state
+ * and an "exit" action that is performed on exit from the state.
+ */
+ entry 'perform self test';
+ do 'provide power';
+ exit 'apply parking brake';
+ }
+ transition 'on-off' first on accept 'Vehicle Off Signal' then off;
+ }
+ state 'health states' {
+ /*
+ * 'health states' is concurrent with 'operational states', because the
+ * containing state usage is "parallel".
+ */
+ entry action initial;
+ do 'sense temperature' {
+ out temp;
+ /*
+ * State-behavior actions may have input and output parameters.
+ */
+ }
+ transition initial then normal;
+ state normal;
+ transition 'normal-maintenance' first normal accept at vehicle1_c1.maintenanceTime then maintenance;
+ transition 'normal-degraded' first normal accept when 'sense temperature'.temp > vehicle1_c1.Tmax do send new 'Over Temp'() to vehicle1_c1.vehicleController then degraded;
+ state maintenance;
+ transition 'maintenance-normal' first maintenance accept 'Return to Normal' then normal;
+ state degraded;
+ transition 'degraded-normal' first degraded accept 'Return to Normal' then normal;
+ }
+ }
+ state 'controller states': 'Controller States' parallel {
+ state 'operational controller states' {
+ entry action initial;
+ transition initial then off;
+ state off;
+ transition 'off-on' first off accept 'Start Signal' then on;
+ state on;
+ transition 'on-off' first on accept 'Off Signal' then off;
+ }
+ }
+ part vehicle1_c1: VehicleA {
+ port fuelCmdPort {
+ in fuelCmd: FuelCmd;
+ }
+ /*
+ * These attribute properties are used in the specification for
+ * 'vehicle states'.
+ */
+ attribute 'brake pedal depressed': Boolean;
+ attribute maintenanceTime: Time::DateTime;
+ attribute Tmax: ThermodynamicTemperatureValue;
+ perform 'provide power' :>> VehicleA::'provide power' {
+ /*
+ * In the context of the 'vehicle1_c1' part, the 'provide power' action
+ * that is enabled in 'vehicle states' gets its input from the 'fuelCmdPort'.
+ */
+ in fuelCmd = fuelCmdPort.fuelCmd;
+ }
+ exhibit 'vehicle states' :>> VehicleA::'vehicle states' {
+ /*
+ * This allocates the state usage 'vehicle states' as the detailed
+ * state-based behavior for 'vehicle1_c1' that fills in the generic
+ * declaration in 'VehicleA'.
+ */
+ }
+ part vehicleController: VehicleController {
+ exhibit 'controller states' :>> VehicleController::'controller states';
+ }
+ }
+ }
+}
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
new file mode 100644
index 00000000..55998edf
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-1a.sysml
@@ -0,0 +1,158 @@
+package '5-State-based Behavior-1a' {
+ private import ScalarValues::*;
+ private import ISQ::*;
+ package Definitions {
+ part def VehicleA {
+ /*
+ * The following declare that 'VehicleA' performs a
+ * 'provide power' action and exhibits some 'vehicle states',
+ * without giving details about these behaviors.
+ */
+ perform action 'provide power': 'Provide Power';
+ exhibit state 'vehicle states': 'Vehicle States';
+ }
+ part def VehicleController {
+ exhibit state 'controller states': 'Controller States';
+ }
+ /*
+ * Black box specifications for state definitions may also have
+ * input and output parameters, like activities, though none
+ * are used here.
+ */
+ state def 'Vehicle States';
+ state def 'Controller States';
+ action def 'Provide Power';
+ action def 'Perform Self Test';
+ action def 'Apply Parking Brake';
+ action def 'Sense Temperature' {
+ out temp: ThermodynamicTemperatureValue;
+ }
+ attribute def FuelCmd;
+ attribute def 'Vehicle Start Signal';
+ attribute def 'Vehicle On Signal';
+ attribute def 'Vehicle Off Signal';
+ attribute def 'Start Signal';
+ attribute def 'Off Signal';
+ attribute def 'Over Temp';
+ attribute def 'Return to Normal';
+ }
+ package Usages {
+ private import Definitions::*;
+ /*
+ * These actions are used enabled in the state usage
+ * 'vehicle states', in addition to 'provide power'.
+ */
+ action 'provide power': 'Provide Power';
+ action 'perform self test': 'Perform Self Test';
+ action 'apply parking brake': 'Apply Parking Brake';
+ action 'sense temperature': 'Sense Temperature';
+ state 'vehicle states': 'Vehicle States' parallel {
+ /*
+ * This is a usage of the state definition 'Vehicle States'.
+ * Note that it depends specifically on on the part 'vehicle1_c1'.
+ */
+ state 'operational states' {
+ doc
+ /*
+ * The state definition for this usage is implicit.
+ */
+
+ entry action initial {
+ doc
+ /*
+ * This empty entry action acts like a start pseudo state.
+ */
+
+ }
+ transition initial then off;
+ state off;
+ transition 'off-starting' first off accept 'Vehicle Start Signal' if vehicle1_c1.'brake pedal depressed' do send new 'Start Signal'() to vehicle1_c1.vehicleController then starting {
+ /*
+ * The transition definition for a transition usage is always implicit.
+ * "accept" marks the trigger, "if" the guard and "do" the effect.
+ *
+ * The notation "'new Start Signal'()" constructs a specific instance of the
+ * 'Start Signal' attribute def to be sent to the 'vehicleController'. If the
+ * attribute def had properties, their values would be given as arguments
+ * inside the parentheses.
+ */
+ }
+ state starting;
+ transition 'starting-on' first starting accept 'Vehicle On Signal' then on;
+ state on {
+ /*
+ * A state may have a "entry" action that is performed on entry into
+ * the state, a "do" action that is performed while in the state
+ * and an "exit" action that is performed on exit from the state.
+ */
+ entry 'perform self test';
+ do 'provide power';
+ exit 'apply parking brake';
+ }
+ transition 'on-off' first on accept 'Vehicle Off Signal' then off;
+ }
+ state 'health states' {
+ /*
+ * 'health states' is concurrent with 'operational states', because the
+ * containing state usage is "parallel".
+ */
+ entry action initial;
+ do 'sense temperature' {
+ out temp;
+ /*
+ * State-behavior actions may have input and output parameters.
+ */
+ }
+ transition initial then normal;
+ state normal;
+ transition 'normal-maintenance' first normal accept at vehicle1_c1.maintenanceTime then maintenance;
+ transition 'normal-degraded' first normal accept when 'sense temperature'.temp > vehicle1_c1.Tmax do send new 'Over Temp'() to vehicle1_c1.vehicleController then degraded;
+ state maintenance;
+ transition 'maintenance-normal' first maintenance accept 'Return to Normal' then normal;
+ state degraded;
+ transition 'degraded-normal' first degraded accept 'Return to Normal' then normal;
+ }
+ }
+ state 'controller states': 'Controller States' parallel {
+ state 'operational controller states' {
+ entry action initial;
+ transition initial then off;
+ state off;
+ transition 'off-on' first off accept 'Start Signal' then on;
+ state on;
+ transition 'on-off' first on accept 'Off Signal' then off;
+ }
+ }
+ part vehicle1_c1: VehicleA {
+ port fuelCmdPort {
+ in fuelCmd: FuelCmd;
+ }
+ /*
+ * These attribute properties are used in the specification for
+ * 'vehicle states'.
+ */
+ attribute 'brake pedal depressed': Boolean;
+ attribute maintenanceTime: Time::DateTime;
+ attribute Tmax: ThermodynamicTemperatureValue;
+ perform 'provide power' :>> VehicleA::'provide power' {
+ doc
+ /*
+ * In the context of the 'vehicle1_c1' part, the 'provide power' action
+ * that is enabled in 'vehicle states' gets its input from the 'fuelCmdPort'.
+ */
+
+ in fuelCmd = fuelCmdPort.fuelCmd;
+ }
+ exhibit 'vehicle states' :>> VehicleA::'vehicle states' {
+ /*
+ * This allocates the state usage 'vehicle states' as the detailed
+ * state-based behavior for 'vehicle1_c1' that fills in the generic
+ * declaration in 'VehicleA'.
+ */
+ }
+ part vehicleController: VehicleController {
+ exhibit 'controller states' :>> VehicleController::'controller states';
+ }
+ }
+ }
+}
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
new file mode 100644
index 00000000..2e7a7f0d
--- /dev/null
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Expected/05-State-based Behavior/5-State-based Behavior-2.sysml
@@ -0,0 +1,97 @@
+package '5-State-based Behavior-2' {
+ private import ScalarValues::*;
+ private import ISQ::*;
+ private import '3a-Function-based Behavior-1'::*;
+ package Definitions {
+ part def VehicleA {
+ perform action 'provide power': 'Provide Power';
+ exhibit state 'vehicle states': 'Vehicle States';
+ }
+ part def VehicleController {
+ exhibit state 'controller states': 'Controller States';
+ }
+ state def 'Vehicle States';
+ state def 'Controller States';
+ action def 'Perform Self Test';
+ action def 'Apply Parking Brake';
+ action def 'Sense Temperature' {
+ out temp: ThermodynamicTemperatureValue;
+ }
+ attribute def 'Vehicle Start Signal';
+ attribute def 'Vehicle On Signal';
+ attribute def 'Vehicle Off Signal';
+ attribute def 'Start Signal';
+ attribute def 'Off Signal';
+ attribute def 'Over Temp';
+ attribute def 'Return to Normal';
+ }
+ package Usages {
+ private import Definitions::*;
+ action 'perform self test': 'Perform Self Test';
+ action 'apply parking brake': 'Apply Parking Brake';
+ action 'sense temperature': 'Sense Temperature';
+ state 'vehicle states': 'Vehicle States' parallel {
+ state 'operational states' {
+ entry;
+ then off;
+ /*
+ * The following uses a shorthand for a transition whose source
+ * is the immediately preceding state.
+ */
+ state off;
+ accept 'Vehicle Start Signal' if vehicle1_c1.'brake pedal depressed' do send new 'Start Signal'() to vehicle1_c1.vehicleController then starting;
+ state starting;
+ accept 'Vehicle On Signal' then on;
+ state on {
+ entry 'perform self test';
+ do 'provide power';
+ exit 'apply parking brake';
+ }
+ accept 'Vehicle Off Signal' then off;
+ }
+ state 'health states' {
+ entry;
+ then normal;
+ do 'sense temperature' {
+ out temp;
+ }
+ /*
+ * The shorthand can be used for multiple transitions after
+ * a single state.
+ */
+ state normal;
+ accept at vehicle1_c1.maintenanceTime then maintenance;
+ accept when 'sense temperature'.temp > vehicle1_c1.Tmax do send new 'Over Temp'() to vehicle1_c1.vehicleController then degraded;
+ state maintenance;
+ accept 'Return to Normal' then normal;
+ state degraded;
+ accept 'Return to Normal' then normal;
+ }
+ }
+ state 'controller states': 'Controller States' parallel {
+ state 'operational controller states' {
+ entry;
+ then off;
+ state off;
+ accept 'Start Signal' then on;
+ state on;
+ accept 'Off Signal' then off;
+ }
+ }
+ part vehicle1_c1: VehicleA {
+ port fuelCmdPort {
+ in fuelCmd: FuelCmd;
+ }
+ attribute 'brake pedal depressed': Boolean;
+ attribute maintenanceTime: Time::DateTime;
+ attribute Tmax: ThermodynamicTemperatureValue;
+ perform 'provide power' :>> VehicleA::'provide power' {
+ in fuelCmd = fuelCmdPort.fuelCmd;
+ }
+ exhibit 'vehicle states' :>> VehicleA::'vehicle states';
+ part vehicleController: VehicleController {
+ exhibit 'controller states' :>> VehicleController::'controller states';
+ }
+ }
+ }
+}
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/SysML2.NET.Serializer.TextualNotation.Tests.csproj b/SysML2.NET.Serializer.TextualNotation.Tests/SysML2.NET.Serializer.TextualNotation.Tests.csproj
index 52805693..8007df35 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/SysML2.NET.Serializer.TextualNotation.Tests.csproj
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/SysML2.NET.Serializer.TextualNotation.Tests.csproj
@@ -52,88 +52,10 @@
-
+
Always
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
+
Always
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/01-Parts Tree/1a-Parts Tree.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/01-Parts Tree/1a-Parts Tree.sysmlx
index 4ac4c4a7..618de027 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/01-Parts Tree/1a-Parts Tree.sysmlx
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/01-Parts Tree/1a-Parts Tree.sysmlx
@@ -1,407 +1,407 @@
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
+
+
diff --git a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/01-Parts Tree/1c-Parts Tree Redefinition.sysmlx b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/01-Parts Tree/1c-Parts Tree Redefinition.sysmlx
index 4e7e596d..94965a90 100644
--- a/SysML2.NET.Serializer.TextualNotation.Tests/Validation/01-Parts Tree/1c-Parts Tree Redefinition.sysmlx
+++ b/SysML2.NET.Serializer.TextualNotation.Tests/Validation/01-Parts Tree/1c-Parts Tree Redefinition.sysmlx
@@ -1,277 +1,277 @@
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+