Skip to content
Open
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
107 changes: 107 additions & 0 deletions Resources/Quantities.sysml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
standard library package Quantities {
doc
/*
* This package defines the root representations for quantities and their values.
*/

private import Collections::*;
private import ScalarValues::NumericalValue;
private import ScalarValues::Number;
private import ScalarValues::Real;
private import ScalarValues::Natural;
private import ScalarValues::Boolean;
private import ScalarValues::String;
private import VectorValues::NumericalVectorValue;
private import VectorValues::ThreeVectorValue;

abstract attribute def TensorQuantityValue :> Array {
doc
/*
* The value of a quantity is a tuple of one or more numbers (i.e. mathematical number values) and a reference to a measurement reference.
* The most general case is a multi-dimensional, tensor quantity of any order. In engineering, the majority of quantities used are
* scalar and vector quantities, that are tensor quantities of order 0 and 1 respectively.
* The measurement reference used to express a quantity value must have a type, dimensions and order that match the quantity, i.e.,
* a TensorQuantityValue must use a TensorMeasurementReference, a VectorQuantityValue a VectorMeasurementReference,
* and a ScalarQuantityValue a ScalarMeasurementReference. See package MeasurementReferences for details.
*/

attribute isBound: Boolean;
attribute num: Number[1..*] ordered nonunique :>> elements;
attribute mRef: MeasurementReferences::TensorMeasurementReference;
attribute :>> dimensions = mRef.dimensions;
attribute order :>> rank;
attribute contravariantOrder: Natural;
attribute covariantOrder: Natural;

assert constraint orderSum { contravariantOrder + covariantOrder == order }
assert constraint boundMatch { (isBound == mRef.isBound) or (not isBound and mRef.isBound) }
}

abstract attribute def VectorQuantityValue :> TensorQuantityValue, NumericalVectorValue {
attribute :>> mRef: MeasurementReferences::VectorMeasurementReference;
}

abstract attribute def ScalarQuantityValue :> VectorQuantityValue, NumericalValue {
attribute :>> mRef: MeasurementReferences::ScalarMeasurementReference;
}

abstract attribute tensorQuantities: TensorQuantityValue[*] nonunique {
doc
/*
* Quantities are defined as self-standing features that can be used to consistently specify quantities as
* features of occurrences. Each single quantity feature is subsetting the root feature tensorQuantities.
* In other words, the codomain of a quantity feature is a suitable specialization of TensorQuantityValue.
*/
}
abstract attribute vectorQuantities: VectorQuantityValue[*] nonunique :> tensorQuantities;
abstract attribute scalarQuantities: ScalarQuantityValue[*] nonunique :> vectorQuantities;

abstract attribute def '3dVectorQuantityValue' :> VectorQuantityValue, ThreeVectorValue {
doc
/*
* Most general representation of real 3-vector quantities
*/

attribute :>> num: Real[3];
}
alias ThreeDVectorQuantityValue for '3dVectorQuantityValue';

/*
* Define generic aliases QuantityValue and quantities for the top level quantity attribute def and attribute.
*/
alias QuantityValue for TensorQuantityValue;
alias quantities for tensorQuantities;

attribute def SystemOfQuantities {
doc
/*
* A SystemOfQuantities represents the essentials of [VIM] concept "system of quantities" (https://jcgm.bipm.org/vim/en/1.3.html), defined as a
* "set of quantities together with a set of noncontradictory equations relating those quantities".
* In order to establish such a set of noncontradictory equations a set of base quantities is selected. Subsequently the system of quantities is
* completed by adding derived quantities which are products of powers of the base quantities.
*/

attribute baseQuantities: ScalarQuantityValue[*] ordered :> scalarQuantities;
}

attribute def QuantityPowerFactor {
doc
/*
* Representation of a quantity power factor, being the combination of a quantity and an exponent.
*
* A sequence of QuantityPowerFactors for the baseQuantities of a SystemOfQuantities define the QuantityDimension of a scalar quantity.
*/

attribute quantity: ScalarQuantityValue[1];
attribute exponent: Real[1];
}

attribute def QuantityDimension {
doc
/*
* Representation of quantity dimension, which is the product of powers of the set of base quantities defined for a particular system of quantities, units and scales.
*/

attribute quantityPowerFactors: QuantityPowerFactor[*] ordered;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,41 @@ public async Task VerifyCanGenerateTextualNotation()
{
await Assert.ThatAsync(() => this.umlCoreTextualNotationBuilderGenerator.GenerateAsync(GeneratorSetupFixture.XmiReaderResult, this.textualNotationSpecification, this.umlPocoDirectoryInfo), Throws.Nothing);
}

/// <summary>
/// Regression for the subtype-overlap guard-synthesis fix. The <c>OwnedExpression</c>
/// dispatch in <c>BuildOwnedExpression</c> groups seven rules that target
/// <c>OperatorExpression</c> and pairs them with a sibling alternative
/// (<c>PrimaryExpression</c>) that targets a SUPERTYPE (<c>Expression</c>). Before the
/// fix, the would-be-default of the duplicate group (<c>ExtentExpression</c>) was emitted
/// as a bare <c>case IOperatorExpression pocoOperatorExpression:</c> which greedily
/// swallowed <c>IFeatureChainExpression</c> (an <c>IOperatorExpression</c> subtype)
/// before it could reach <c>default → BuildPrimaryExpression</c>. The fix synthesises a
/// <c>when</c>-clause from the rule's parsed assignments — <c>operator = 'all'</c> and
/// <c>ownedRelationship += TypeReferenceMember</c> — so the case becomes
/// <c>case IOperatorExpression … when … .Operator == "all" &amp;&amp; … .Current is IParameterMembership:</c>
/// and <c>FeatureChainExpression</c> falls through to the correct dispatcher. This test
/// pins both halves of the synthesised guard so future regressions are caught.
/// </summary>
[Test]
public async Task Verify_that_ExtentExpression_case_carries_synthesised_guard()
{
await this.umlCoreTextualNotationBuilderGenerator.GenerateAsync(GeneratorSetupFixture.XmiReaderResult, this.textualNotationSpecification, this.umlPocoDirectoryInfo);

var generatedExpressionBuilderPath = Path.Combine(this.umlPocoDirectoryInfo.FullName, "ExpressionTextualNotationBuilder.cs");
Assert.That(File.Exists(generatedExpressionBuilderPath), Is.True, $"Expected generator to emit {generatedExpressionBuilderPath}");

var generatedSource = await File.ReadAllTextAsync(generatedExpressionBuilderPath);

Assert.Multiple(() =>
{
Assert.That(generatedSource, Does.Not.Contain("case SysML2.NET.Core.POCO.Kernel.Expressions.IOperatorExpression pocoOperatorExpression:" + System.Environment.NewLine + " OperatorExpressionTextualNotationBuilder.BuildExtentExpression"),
"ExtentExpression's case must not be emitted as the bare unguarded `case IOperatorExpression pocoOperatorExpression:` fall-through — it would swallow IFeatureChainExpression.");
Assert.That(generatedSource, Does.Contain(".Operator == \"all\""),
"Synthesised guard for ExtentExpression must include the parsed scalar literal `operator = 'all'` as `.Operator == \"all\"`.");
Assert.That(generatedSource, Does.Contain(".Current is SysML2.NET.Core.POCO.Kernel.Behaviors.IParameterMembership"),
"Synthesised guard for ExtentExpression must include the parsed `ownedRelationship += TypeReferenceMember` cursor predicate.");
});
}
}
}
12 changes: 10 additions & 2 deletions SysML2.NET.CodeGenerator/GRAMMAR.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ SysML2 grammar rules (in `Grammar/Resources/*.kebnf` and the `<para>…</para>`
| Scalar assignment | `prop = X` | Assign the parsed value of `X` to the property `prop` |
| Collection assignment | `prop += X` | Append one parsed `X` to the collection `prop` |
| Boolean assignment | `prop ?= 'keyword'` | Set `prop = true` when the terminal is present |
| Non-parsing assignment | `{ prop = 'val' }` | Implicit side-effect in parse direction; in unparse direction it emits no output |
| Non-parsing assignment | `{ prop = 'val' }` | Implicit side-effect in parse direction; in unparse direction it emits no output, and it does NOT participate in dispatch-guard synthesis — only parsed assignments (`prop = X`, `prop += X`, `prop ?= X`) do |
| QualifiedName value literal | `prop = [QualifiedName]` | Cross-reference by qualified name |

## Pipeline Overview
Expand Down Expand Up @@ -115,7 +115,15 @@ When multiple alternatives map to the same UML class (creating duplicate switch

1. **`?=` boolean guards** (primary) — e.g., `EndUsagePrefix` has `isEnd?='end'`, so it gets `when poco.IsEnd`
2. **`IsValidFor{RuleName}()` extension methods** (fallback) — hand-coded in `MembershipValidationExtensions.cs` or `TextualNotationValidationExtensions.cs`. Used when `?=` can't disambiguate
3. **Type ordering** — more specific types (deeper inheritance) come first, fallback case (matching `NamedElementToGenerate`) goes last as `default:`
3. **Synthesised structural guards** (subtype-overlap defence) — when a duplicate group's target class has subtypes routed by a sibling alternative (i.e. another alternative targets a SUPERTYPE of the group's target), the would-be-default member is NOT left as a bare `case I{Target}:`. Instead, `RuleProcessor.PatternHandlers.cs#SynthesiseGuardFromRuleBody` walks the rule body and AND-combines one predicate per parsed `AssignmentElement`:
- `prop = 'literal'` → `poco.{Prop} == "literal"`
- `prop = [QualifiedName]` → `poco.{Prop} != null`
- `prop = NonTerminal` → `poco.{Prop} is I{RHS-target}` (or `!= null` when the RHS target cannot be resolved)
- first `ownedRelationship += NonTerminal` → `cursor.Current is I{RHS-target}`
- non-cursor `prop += NonTerminal` → `poco.{Prop}.OfType<I{RHS-target}>().Any()`
- `prop ?= 'kw'` → produced by step 1, not re-synthesised here
- `{ prop = X }` non-parsing → ignored
4. **Type ordering** — more specific types (deeper inheritance) come first, fallback case (matching `NamedElementToGenerate`) goes last as `default:`

## Patterns Handled by Code-Gen

Expand Down
Loading
Loading