Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,17 @@ private string ResolveContentTypeGuard(string cursorVariableName, TextualNotatio
return null;
}

// An allowlisted content rule needs an ABSENCE constraint on the referenced element's own
// contents, which body-shape analysis cannot express — delegate to the hand-coded predicate
// rather than emitting the shape-derived type check below.
if (RequiresHandCodedContentGuard(referencedRule.RuleName))
{
var handCodedGuardVariableName = $"{referencedRule.RuleName.LowerCaseFirstLetter()}Guard{ruleGenerationContext.NarrowedTypeCheckCounter}";
ruleGenerationContext.NarrowedTypeCheckCounter++;

return $"{cursorVariableName}.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship {handCodedGuardVariableName} && {handCodedGuardVariableName}.IsValidFor{referencedRule.RuleName}(writerContext)";
}

var outerTargetName = referencedRule.EffectiveTarget;
var outerTargetClass = RuleQueryUtilities.FindClass(umlClass.Cache, outerTargetName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -812,26 +812,25 @@
var defaultElement = mappedNonTerminalElements
.LastOrDefault(x => x.UmlClass == ruleGenerationContext.NamedElementToGenerate && !whenGuards.ContainsKey(x.RuleElement));

mappedNonTerminalElements.Sort((a, b) =>
{
var aIsDefault = defaultElement.RuleElement != null && a.RuleElement == defaultElement.RuleElement;
var bIsDefault = defaultElement.RuleElement != null && b.RuleElement == defaultElement.RuleElement;

if (aIsDefault && !bIsDefault)
{
return 1;
}

if (bIsDefault && !aIsDefault)
{
return -1;
}

var depthA = a.UmlClass.QueryAllGeneralClassifiers().Count;
var depthB = b.UmlClass.QueryAllGeneralClassifiers().Count;

return depthB.CompareTo(depthA);
});
// Ordered by: non-default arms first (the rule's own target class is the catch-all and
// must sit last), then most-derived first so a subtype arm always precedes an arm
// targeting its supertype.
//
// OrderBy/ThenByDescending is STABLE, which is load-bearing rather than incidental:
// arms of equal inheritance depth are mutually disjoint, so their relative order does
// not affect dispatch — but it does affect the emitted TEXT. The previous
// List<T>.Sort is introsort and therefore unstable, and the comparison carried no
// secondary key, so adding one alternative anywhere in a rule could reshuffle unrelated
// equal-depth arms and produce diff noise that reads like a behavioural change but is
// not. Adding AllocationDefinition to DefinitionElement did exactly that, silently
// reordering MetadataDefinition / ViewDefinition / RenderingDefinition. Falling back to
// declaration order keeps every regeneration minimal and deterministic.
mappedNonTerminalElements =
[
.. mappedNonTerminalElements
.OrderBy(element => defaultElement.RuleElement != null && element.RuleElement == defaultElement.RuleElement)
.ThenByDescending(element => element.UmlClass.QueryAllGeneralClassifiers().Count)
];

var variableName = "poco";

Expand Down Expand Up @@ -1574,7 +1573,7 @@
return null;
}

// Likewise a `+=` property may be a SCALAR on the outer class (e.g. ISubsetting.Specific);

Check warning on line 1576 in SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this commented out code.

Check warning on line 1576 in SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.PatternHandlers.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this commented out code.
// an OfType<…>().Any() clause would not compile there.
if (assignment.Operator == "+=" && !matchingProperty.QueryIsEnumerable())
{
Expand Down
21 changes: 21 additions & 0 deletions SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1193,7 +1193,7 @@
writer.WriteSafeString($"{subclass.Name}TextualNotationBuilder.Build{nonTerminalElement.Name}({patternVariableName}, writerContext, stringBuilder);{Environment.NewLine}");
writer.WriteSafeString($"}}{Environment.NewLine}");

// A NonTerminal-valued assignment emits its own null guard inside ProcessAssignmentElement;

Check warning on line 1196 in SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this commented out code.

Check warning on line 1196 in SysML2.NET.CodeGenerator/HandleBarHelpers/RuleProcessor.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this commented out code.
// only a value-literal assignment (e.g. [QualifiedName]) needs the guard supplied here.
if (assignmentElement.Value is ValueLiteralElement)
{
Expand Down Expand Up @@ -1688,5 +1688,26 @@
{
return string.Equals(alternativeRuleName, "FunctionOperationExpression", StringComparison.Ordinal);
}

/// <summary>
/// Returns true when a collection loop's CONTENT guard cannot be derived from the referenced rule's
/// body shape and must be supplied by a hand-coded <c>IsValidFor{Rule}</c> guard.
/// </summary>
/// <remarks>
/// Currently <c>PrefixMetadataMember</c>. The synthesised guard tests only the shape the rule states
/// — an <c>OwningMembership</c> whose <c>ownedRelatedElement</c> contains a <c>MetadataUsage</c> — but
/// what makes the prefix form applicable is what the usage does NOT own. <c>PrefixMetadataUsage :
/// MetadataUsage = ownedRelationship += OwnedFeatureTyping</c> has no <c>MetadataUsageDeclaration</c>
/// and no <c>MetadataBody</c>, so a usage carrying a body (<c>@Safety { ref :&gt;&gt; isMandatory =
/// false; }</c>) cannot be written with <c>#</c> and must fall through to the body form. Body-shape
/// analysis cannot express an ABSENCE constraint on the referenced element's own contents, so the
/// predicate is hand-coded.
/// </remarks>
/// <param name="contentRuleName">The KEBNF rule name supplying the loop's content</param>
/// <returns><c>true</c> if the codegen should emit a hand-coded <c>IsValidFor{Rule}</c> guard</returns>
private static bool RequiresHandCodedContentGuard(string contentRuleName)
{
return string.Equals(contentRuleName, "PrefixMetadataMember", StringComparison.Ordinal);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ package '10c-Fuel Economy Analysis' {
require constraint { actualFuelEconomy >= requiredFuelEconomy }
}
requirement cityFuelEconomyRequirement: FuelEconomyRequirement {
:>> requiredFuelEconomy = 25[(mi / gallon)];
:>> requiredFuelEconomy = 25[mi / gallon];
}
requirement highwayFuelEconomyRequirement: FuelEconomyRequirement {
:>> requiredFuelEconomy = 30[(mi / gallon)];
:>> requiredFuelEconomy = 30[mi / gallon];
}
}
package VehicleDesignModel {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package '12b-Allocation-1' {
package RequirementModel {
requirement torqueGeneration {
subject generator: TorqueGenerator;
require constraint { generator.generateTorque.torque > 0.0[(N * m)] }
require constraint { generator.generateTorque.torque > 0.0[N * m] }
}
}
package LogicalModel {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package '13a-Model Containment' {
private import '2a-Parts Interconnection'::*;
private import '8-Requirements'::*;
requirement BodyAndInteriorRequirements {
public import '1';
}
requirement PowerTrainRequirements;
package 'Vehicle Model' {
doc
/*
* This package is used to represent a top-level "model".
* There is no specific syntax for identifying a package
* used in this way.
*/

package 'Vehicle1-Configuration' {
alias 'Sport Sedan' for Usages::vehicle1_c1;
public import 'vehicle1_c1 Specification Context'::'vehicle1-c1 Specification';
}
package 'Vehicle Reference Model' {
doc
/*
* This package is used to represent a "model library".
* There is no specific syntax for identifying a package
* used in this way.
*/

public import VehicleA;
public import VehicleSubsystems;
}
package VehicleSubsystems {
public import 'Body&Interior';
public import PowerTrain;
}
package 'Body&Interior' {
public import BodyAndInteriorRequirements;
}
package PowerTrain {
public import Definitions::Engine;
public import Definitions::Transmission;
public import PowerTrainRequirements;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package '13b-Safety and Security Features Element Group-1' {
private import ScalarValues::*;
private import AnnotationDefinitions::*;
private import PartsTree::*;
package AnnotationDefinitions {
metadata def Safety {
attribute isMandatory: Boolean;
}
metadata def Security;
}
package PartsTree {
part vehicle {
part interior {
#Security part alarm;
part seatBelt[2] {
@ Safety {
ref :>> isMandatory = true;
}
}
part frontSeat[2];
part driverAirBag {
@ Safety {
ref :>> isMandatory = false;
}
}
}
part bodyAssy {
part body;
part bumper {
@ Safety {
ref :>> isMandatory = true;
}
}
#Security part keylessEntry;
}
part wheelAssy {
part wheel[2];
part antilockBrakes[2] {
@ Safety {
ref :>> isMandatory = false;
}
}
}
}
}
package 'Safety Features' {
/* Parts that contribute to safety. */
public import vehicle::**;
filter @ Safety;
}
package 'Security Features' {
/* Parts that contribute to security. */
public import vehicle::**;
filter @ Security;
}
package 'Safety & Security Features' {
/* Parts that contribute to safety OR security. */
public import vehicle::**;
filter @ Safety or @ Security;
}
package 'Mandatory Safety Features' {
/* Parts that contribute to safety AND are mandatory. */
public import vehicle::**;
filter @ Safety and (as Safety).isMandatory;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package '13b-Safety and Security Features Element Group-2' {
private import ScalarValues::*;
private import AnnotationDefinitions::*;
private import PartsTree::*;
package AnnotationDefinitions {
metadata def Safety {
attribute isMandatory: Boolean;
}
metadata def Security;
}
package PartsTree {
part vehicle {
part interior {
#Security part alarm;
part seatBelt[2] {
@ Safety {
ref :>> isMandatory = true;
}
}
part frontSeat[2];
part driverAirBag {
@ Safety {
ref :>> isMandatory = false;
}
}
}
part bodyAssy {
part body;
part bumper {
@ Safety {
ref :>> isMandatory = true;
}
}
#Security part keylessEntry;
}
part wheelAssy {
part wheel[2];
part antilockBrakes[2] {
@ Safety {
ref :>> isMandatory = false;
}
}
}
}
}
package 'Safety Features' {
/* Parts that contribute to safety. */
public import vehicle::**[@ '13b-Safety and Security Features Element Group-2'::AnnotationDefinitions::Safety];
}
package 'Security Features' {
/* Parts that contribute to security. */
public import vehicle::**[@ '13b-Safety and Security Features Element Group-2'::AnnotationDefinitions::Security];
}
package 'Safety & Security Features' {
/* Parts that contribute to safety OR security. */
public import vehicle::**[@ '13b-Safety and Security Features Element Group-2'::AnnotationDefinitions::Safety or @ '13b-Safety and Security Features Element Group-2'::AnnotationDefinitions::Security];
}
package 'Mandatory Saftey Features' {
/* Parts that contribute to safety AND are mandatory. */
public import vehicle::**[@ '13b-Safety and Security Features Element Group-2'::AnnotationDefinitions::Safety and (as '13b-Safety and Security Features Element Group-2'::AnnotationDefinitions::Safety).isMandatory];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package '13b-Safety and Security Features Element Group' {
part vehicle1_c1 {
part interior {
part alarm;
part seatBelt[2];
part frontSeat[2];
part driverAirBag;
}
part bodyAssy {
part body;
part bumper;
part keylessEntry;
}
}
package 'Safety Features' {
/* Parts that contribute to safety. */
public import vehicle1_c1::interior::seatBelt;
public import vehicle1_c1::interior::driverAirBag;
public import vehicle1_c1::bodyAssy::bumper;
}
package 'Security Features' {
/* Parts that contribute to security. */
public import vehicle1_c1::interior::alarm;
public import vehicle1_c1::bodyAssy::keylessEntry;
}
package 'Safety & Security Features' {
/*
* Parts that contribute to safety AND
* parts that contribute to security.
*/
public import 'Safety Features'::*;
public import 'Security Features'::*;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ public void OneTimeTearDown()
[TestCase("12-Dependency Relationships", "12a-Dependency.sysmlx")]
[TestCase("12-Dependency Relationships", "12b-Allocation-1.sysmlx")]
[TestCase("12-Dependency Relationships", "12b-Allocation.sysmlx")]
[TestCase("13-Model Containment", "13a-Model Containment.sysmlx")]
[TestCase("13-Model Containment", "13b-Safety and Security Features Element Group-1.sysmlx")]
[TestCase("13-Model Containment", "13b-Safety and Security Features Element Group-2.sysmlx")]
[TestCase("13-Model Containment", "13b-Safety and Security Features Element Group.sysmlx")]
public async Task VerifyValidationTextualNotationXmi(string folderName, string fileName)
{
var loggerFactory = LoggerFactory.Create(builder =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public static void BuildDefinitionPrefix(SysML2.NET.Core.POCO.Systems.Definition
SharedTextualNotationBuilder.BuildBasicDefinitionPrefix(poco, writerContext, stringBuilder);
}
var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship);
while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType<SysML2.NET.Core.POCO.Systems.Metadata.IMetadataUsage>().Any())
while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext))
{
var positionBeforeItem0 = ownedRelationshipCursor.Position;
BuildDefinitionExtensionKeyword(poco, writerContext, stringBuilder);
Expand Down Expand Up @@ -116,7 +116,7 @@ public static void BuildExtendedDefinition(SysML2.NET.Core.POCO.Systems.Definiti
SharedTextualNotationBuilder.BuildBasicDefinitionPrefix(poco, writerContext, stringBuilder);
}
var ownedRelationshipCursor = writerContext.CursorCache.GetOrCreateCursor(poco.Id, "ownedRelationship", poco.OwnedRelationship);
while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Namespaces.IOwningMembership owningMembershipGuard && owningMembershipGuard.OwnedRelatedElement.OfType<SysML2.NET.Core.POCO.Systems.Metadata.IMetadataUsage>().Any())
while (ownedRelationshipCursor.Current is SysML2.NET.Core.POCO.Root.Elements.IRelationship prefixMetadataMemberGuard0 && prefixMetadataMemberGuard0.IsValidForPrefixMetadataMember(writerContext))
{
var positionBeforeItem0 = ownedRelationshipCursor.Position;
BuildDefinitionExtensionKeyword(poco, writerContext, stringBuilder);
Expand Down
Loading
Loading