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 @@ -59,6 +59,12 @@
/// </summary>
private const string SharedBuilderTemplateName = "core-textual-notation-shared-builder-template";

/// <summary>
/// The root rule from which grammar reachability is computed to flag
/// <see cref="SysML2.NET.Serializer.TextualNotation.Writers.GrammarUnreachableAttribute" /> methods.
/// </summary>
private const string RootRuleName = "RootNamespace";

/// <summary>
/// Register the custom helpers
/// </summary>
Expand Down Expand Up @@ -122,7 +128,7 @@

await this.GenerateBuilderClasses(xmiReaderResult, textualNotationSpecification, outputDirectory);
await this.GenerateSharedBuilder(xmiReaderResult, textualNotationSpecification, outputDirectory);
// await this.GenerateBuilderFacade(xmiReaderResult, outputDirectory);

Check warning on line 131 in SysML2.NET.CodeGenerator/Generators/UmlHandleBarsGenerators/UmlCoreTextualNotationBuilderGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this commented out code.

Check warning on line 131 in SysML2.NET.CodeGenerator/Generators/UmlHandleBarsGenerators/UmlCoreTextualNotationBuilderGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this commented out code.

// Every rule has now been generated, so an invariant whose OMG name resolved against nothing is
// no longer being applied. Reported rather than thrown: the emission rule it backs is off, which
Expand Down Expand Up @@ -173,6 +179,10 @@
{
var template = this.Templates[BuilderTemplateName];

var unreachableRuleNames = textualNotationSpecification.ComputeUnreachableRuleNames(RootRuleName)
.Except(RulesHelper.HandCodedReachableRuleNames)
.ToHashSet();

var namedElements = xmiReaderResult.QueryContainedAndImported("SysML")
.SelectMany(x => x.PackagedElement.OfType<INamedElement>())
.ToList();
Expand Down Expand Up @@ -202,7 +212,7 @@
{
var targetClassContext = namedElements.Single(x => x.Name == rulesPerType.Key);

var generatedBuilder = template(new {Context = targetClassContext, Rules = rulesPerType.Value, AllRules = textualNotationSpecification.Rules});
var generatedBuilder = template(new {Context = targetClassContext, Rules = rulesPerType.Value, AllRules = textualNotationSpecification.Rules, UnreachableRuleNames = unreachableRuleNames});
generatedBuilder = this.CodeCleanup(generatedBuilder);

var fileName = $"{targetClassContext.Name.CapitalizeFirstLetter()}TextualNotationBuilder.cs";
Expand Down Expand Up @@ -282,7 +292,11 @@
return;
}

var generatedBuilder = template(new { Entries = entries, AllRules = textualNotationSpecification.Rules });
var unreachableRuleNames = textualNotationSpecification.ComputeUnreachableRuleNames(RootRuleName)
.Except(RulesHelper.HandCodedReachableRuleNames)
.ToHashSet();

var generatedBuilder = template(new { Entries = entries, AllRules = textualNotationSpecification.Rules, UnreachableRuleNames = unreachableRuleNames });
generatedBuilder = this.CodeCleanup(generatedBuilder);

await WriteAsync(generatedBuilder, outputDirectory, $"{RulesHelper.SharedBuilderClassName}.cs");
Expand All @@ -296,7 +310,7 @@
/// <param name="outputDirectory">The target <see cref="DirectoryInfo"/></param>
/// <exception cref="ArgumentNullException">If one of the given parameters is null</exception>
/// <returns>an awaitable <see cref="Task"/></returns>
private Task GenerateBuilderFacade(XmiReaderResult xmiReaderResult, DirectoryInfo outputDirectory)

Check warning on line 313 in SysML2.NET.CodeGenerator/Generators/UmlHandleBarsGenerators/UmlCoreTextualNotationBuilderGenerator.cs

View workflow job for this annotation

GitHub Actions / Build

Remove the unused private method 'GenerateBuilderFacade'.
{
ArgumentNullException.ThrowIfNull(xmiReaderResult);
ArgumentNullException.ThrowIfNull(outputDirectory);
Expand Down
83 changes: 83 additions & 0 deletions SysML2.NET.CodeGenerator/Grammar/Model/TextualNotationRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -359,5 +359,88 @@ when string.Equals(assignmentElement.Property, propertyName, StringComparison.Or
}
}
}

/// <summary>
/// Recursively resolves the names of all rules transitively reachable from this rule via
/// NonTerminal references, including this rule itself.
/// </summary>
/// <param name="allRules">All available rules for resolving NonTerminal references</param>
/// <returns>The set of reachable rule names</returns>
public IReadOnlySet<string> QueryReachableRuleNames(IReadOnlyList<TextualNotationRule> allRules)
{
var visited = new HashSet<string>();
CollectReachableRuleNames(this, allRules, visited);
return visited;
}

/// <summary>
/// Recursively collects the names of rules reachable from <paramref name="rule"/>
/// </summary>
/// <param name="rule">The rule to inspect</param>
/// <param name="allRules">All available rules for resolving NonTerminal references</param>
/// <param name="visited">The accumulated set of reachable rule names</param>
private static void CollectReachableRuleNames(TextualNotationRule rule, IReadOnlyList<TextualNotationRule> allRules, HashSet<string> visited)
{
if (!visited.Add(rule.RuleName))
{
return;
}

foreach (var alternative in rule.Alternatives)
{
CollectReachableRuleNamesFromElements(alternative.Elements, allRules, visited);
}
}

/// <summary>
/// Recursively collects reachable rule names from a list of <see cref="RuleElement"/>
/// </summary>
/// <param name="elements">The elements to inspect</param>
/// <param name="allRules">All available rules for resolving NonTerminal references</param>
/// <param name="visited">The accumulated set of reachable rule names</param>
private static void CollectReachableRuleNamesFromElements(IEnumerable<RuleElement> elements, IReadOnlyList<TextualNotationRule> allRules, HashSet<string> visited)
{
foreach (var element in elements)
{
switch (element)
{
case AssignmentElement { Value: NonTerminalElement valueNonTerminal }:
var valueRule = allRules.SingleOrDefault(x => x.RuleName == valueNonTerminal.Name);

if (valueRule != null)
{
CollectReachableRuleNames(valueRule, allRules, visited);
}

break;

case AssignmentElement { Value: GroupElement valueGroupElement }:
foreach (var valueGroupAlternative in valueGroupElement.Alternatives)
{
CollectReachableRuleNamesFromElements(valueGroupAlternative.Elements, allRules, visited);
}

break;

case NonTerminalElement nonTerminalElement:
var referencedRule = allRules.SingleOrDefault(x => x.RuleName == nonTerminalElement.Name);

if (referencedRule != null)
{
CollectReachableRuleNames(referencedRule, allRules, visited);
}

break;

case GroupElement groupElement:
foreach (var groupAlternative in groupElement.Alternatives)
{
CollectReachableRuleNamesFromElements(groupAlternative.Elements, allRules, visited);
}

break;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@

namespace SysML2.NET.CodeGenerator.Grammar.Model
{
using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Provides access to all <see cref="TextualNotationRule" /> defined into the textual notation specification
Expand All @@ -31,5 +33,27 @@ public class TextualNotationSpecification
/// Gets the collection of all <see cref="TextualNotationRule" />
/// </summary>
public List<TextualNotationRule> Rules { get; } = [];

/// <summary>
/// Computes the names of rules that have no incoming reference — directly or transitively —
/// from the rule named <paramref name="rootRuleName"/>, and are therefore unreachable when
/// generating from that root.
/// </summary>
/// <param name="rootRuleName">The name of the root rule (e.g. <c>RootNamespace</c>)</param>
/// <returns>The set of unreachable rule names</returns>
/// <exception cref="ArgumentException">If no rule named <paramref name="rootRuleName"/> exists</exception>
public IReadOnlySet<string> ComputeUnreachableRuleNames(string rootRuleName)
{
var rootRule = this.Rules.SingleOrDefault(x => x.RuleName == rootRuleName);

if (rootRule == null)
{
throw new ArgumentException($"No rule named '{rootRuleName}' exists in this specification.", nameof(rootRuleName));
}

var reachableRuleNames = rootRule.QueryReachableRuleNames(this.Rules);

return this.Rules.Select(x => x.RuleName).Where(name => !reachableRuleNames.Contains(name)).ToHashSet();
}
}
}
28 changes: 28 additions & 0 deletions SysML2.NET.CodeGenerator/HandleBarHelpers/RulesHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,23 @@
{
var processor = new RuleProcessor();

handlebars.RegisterHelper("RulesHelper.IsRuleUnreachable", (_, arguments) =>
{
if (arguments.Length != 2)
{
throw new ArgumentException("RulesHelper.IsRuleUnreachable expects to have 2 arguments");
}

if (arguments[0] is not string ruleName)
{
throw new ArgumentException("RulesHelper.IsRuleUnreachable expects a rule name string as first argument");
}

return arguments[1] is not IReadOnlySet<string> unreachableRuleNames
? throw new ArgumentException("RulesHelper.IsRuleUnreachable expects a set of unreachable rule names as second argument")
: unreachableRuleNames.Contains(ruleName);
});

handlebars.RegisterHelper("RulesHelper.ContainsAnyDispatcherRules", (_, arguments) =>
{
if (arguments.Length != 1)
Expand Down Expand Up @@ -115,7 +132,7 @@
// accumulator — render their <c>{ … }</c> wrapper on a single line per
// the SST tutorial convention (e.g. constraint and expression bodies).
// The three rules that match in the KEBNF are
// <c>FunctionBody</c>, <c>ExpressionBody</c>, and <c>CalculationBody</c>;

Check warning on line 135 in SysML2.NET.CodeGenerator/HandleBarHelpers/RulesHelper.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this commented out code.

Check warning on line 135 in SysML2.NET.CodeGenerator/HandleBarHelpers/RulesHelper.cs

View workflow job for this annotation

GitHub Actions / Build

Remove this commented out code.
// every other brace-bounded rule uses a <c>*</c>-quantified list and
// renders multi-line. The wrapping suppresses AppendLine newlines inside
// the rule body and re-terminates the logical line on exit so the next
Expand Down Expand Up @@ -183,6 +200,17 @@
return string.Equals(ruleName, "FunctionOperationExpression", StringComparison.Ordinal);
}

/// <summary>
/// Rule names the KEBNF-text-only reachability walk cannot see being reached, because the
/// only remaining reference to them lives in a hand-coded Build{Rule}HandCoded companion
/// rather than in the merged grammar. PayloadFeatureMember is called from
/// SharedTextualNotationBuilder.BuildFlowDeclarationHandCoded, which reimplements KerML's
/// FlowDeclaration : Flow - a rule the SysML-overrides-KerML merge (keyed on bare rule name)
/// drops in favour of SysML's unrelated FlowDeclaration : FlowUsage, taking with it the
/// merged grammar's only textual reference to PayloadFeatureMember.
/// </summary>
public static readonly IReadOnlySet<string> HandCodedReachableRuleNames = new HashSet<string> { "PayloadFeatureMember" };

/// <summary>
/// Determines whether <paramref name="rule"/> targets an <c>IOperatorExpression</c>
/// (or any of its subclasses) as the rule's effective metaclass. Used by
Expand Down Expand Up @@ -224,7 +252,7 @@
}

foreach (var alternative in rule.Alternatives.Where(alternative => alternative.Elements.Count == 3))
{

Check warning on line 255 in SysML2.NET.CodeGenerator/HandleBarHelpers/RulesHelper.cs

View workflow job for this annotation

GitHub Actions / Build

Loop should be simplified by calling Select(alternative => alternative.Elements))
if (alternative.Elements[0] is not TerminalElement { Value: "{" })
{
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ namespace SysML2.NET.Serializer.TextualNotation.Writers
/// <param name="poco">The <see cref="{{ #NamedElement.WriteFullyQualifiedTypeName ../this.Context }}" /> from which the rule should be build</param>
/// <param name="writerContext">The <see cref="TextualNotationWriterContext" /> providing the serialization context for the current <paramref name="poco"/></param>
/// <param name="stringBuilder">The <see cref="IndentedStringBuilder" /> that accumulates the entire textual notation with indentation</param>
{{#if (RulesHelper.IsRuleUnreachable rule.RuleName ../this.UnreachableRuleNames)}}
[GrammarUnreachable("No production in the effective SysML v2 textual grammar (SysML definitions override same-named KerML ones) references this rule, so it can never be reached from RootNamespace.")]
{{/if}}
public static void Build{{rule.RuleName}}({{ #NamedElement.WriteFullyQualifiedTypeName ../this.Context }} poco, TextualNotationWriterContext writerContext, IndentedStringBuilder stringBuilder)
{
{{RulesHelper.WriteRule rule ../this.Context ../this.AllRules}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ namespace SysML2.NET.Serializer.TextualNotation.Writers
/// <param name="poco">The <see cref="{{ #NamedElement.WriteFullyQualifiedTypeName entry.TargetClass }}" /> from which the rule should be build</param>
/// <param name="writerContext">The <see cref="TextualNotationWriterContext" /> providing the serialization context for the current <paramref name="poco"/></param>
/// <param name="stringBuilder">The <see cref="IndentedStringBuilder" /> that accumulates the entire textual notation with indentation</param>
{{#if (RulesHelper.IsRuleUnreachable entry.Rule.RuleName ../this.UnreachableRuleNames)}}
[GrammarUnreachable("No production in the effective SysML v2 textual grammar (SysML definitions override same-named KerML ones) references this rule, so it can never be reached from RootNamespace.")]
{{/if}}
public static void Build{{entry.Rule.RuleName}}({{ #NamedElement.WriteFullyQualifiedTypeName entry.TargetClass }} poco, TextualNotationWriterContext writerContext, IndentedStringBuilder stringBuilder)
{
{{RulesHelper.WriteRule entry.Rule entry.TargetClass ../this.AllRules}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package AHFNorway {
doc
/* This is the Norwegian use-case for Arrowhead Framework */
private import AHFProfileLib::*;
private import AHFProfileMetadata::*;
private import AHFCoreLib::**;
private import ScalarValues::*;
#service def APISService {
doc
/* Service design */
attribute :>> SD::serviceDefinition = "APISPullService";
attribute :>> SD::intrfce_protocol = "{JSON}";
attribute :>> SD::serviceURL = "pull";
}
#servicedd port def APIS_DD :> APISService {
doc
/* Service design description with nested protocol-specific ports */
#idd port APIS_HTTP {
out cll: CallGiveItems;
in retrn: ResultGiveItems;
}
#idd port APIS_MQTT {
out pub: Publish;
out retall: Return_AllItems;
in subscr: Subscribe;
}
}
attribute def Publish {
ref nametopic: String;
}
attribute def Subscribe {
ref nametopic: String;
}
attribute def Return_AllItems {
ref itms: String;
}
attribute def Subscribe_giveItems {
ref itms: String;
}
attribute def Return_Ack {
ref ack: Boolean;
}
attribute def CallGiveItems {
ref itms: String;
}
attribute def ResultGiveItems {
ref ack: Boolean;
}
#clouddd AHFNorway_LocalCloudDD :> ArrowheadCore {
#systemdd TellUConsumer {
#servicedd serviceDiscovery: ~ServiceDiscoveryDD;
#servicedd apisp: APIS_DD;
attribute :>> SysD::systemname = "UngerApisClient";
attribute :>> SysD::address = "Unger_network_ip";
attribute :>> SysD::portno = 0;
state TellUbehavior {
entry send new CallGiveItems("All the items") via apisp.APIS_HTTP;
then Wait;
state Wait;
accept rs: ResultGiveItems then Wait;
}
}
#systemdd APISProducer {
#servicedd serviceDiscovery: ~ServiceDiscoveryDD;
#servicedd tellu: ~APIS_DD;
#servicedd apisc: APIS_DD;
:>> SysD::systemname = "PrediktorApisServer";
:>> SysD::address = "Prediktor_network_ip";
:>> SysD::portno = 6565;
attribute x: Boolean;
action giveItems :> SysDD::ServiceMethod {
in itms: String;
out ack: Boolean;
/* Forward itms and return an ack */
first start;
then send new Return_AllItems() via apisc.APIS_MQTT;
ref success = true;
bind ack = success;
}
state APISPbehavior {
entry send new Publish("Return_AllItems") via apisc.APIS_MQTT;
then WaitOnData;
state WaitOnData;
accept cl: CallGiveItems via tellu.APIS_HTTP do action {
first start;
then action giveItems {
in itms = cl.itms;
out ack = x;
}
then send new ResultGiveItems(x) via tellu.APIS_HTTP;
}
then WaitOnData;
}
}
#systemdd APISConsumer {
#servicedd serviceDiscovery: ~ServiceDiscovery;
#servicedd apisp: ~APIS_DD;
:>> SysD::systemname = "TellUClient";
:>> SysD::address = "Prediktor_network_ip";
:>> SysD::portno = 1;
state MQTT_APISP {
entry send new Subscribe("Return_AllItems") via apisp.APIS_MQTT;
then Idle;
state Idle;
accept Return_AllItems via apisp.APIS_MQTT then Idle;
}
}
part MQTTServer {
port getTopic: ~APIS_DD;
port giveTopic: APIS_DD;
state Serve {
entry;
then Publ;
state Publ;
accept pub: Publish via getTopic.APIS_MQTT then Subsr;
state Subsr;
accept Subscribe via giveTopic.APIS_MQTT then Idle;
state Idle;
accept retrnall: Return_AllItems via getTopic.APIS_MQTT do send retrnall via giveTopic.APIS_MQTT then Idle;
}
}
connect APISProducer.apisc to MQTTServer.getTopic;
connect MQTTServer.giveTopic to APISConsumer.apisp;
connect TellUConsumer.apisp to APISProducer.tellu;
connect APISProducer.serviceDiscovery to service_registry.serviceDiscovery;
connect TellUConsumer.serviceDiscovery to service_registry.serviceDiscovery;
connect APISConsumer.serviceDiscovery to service_registry.serviceDiscovery;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package 'Binding Connectors Example-1' {
private import 'Port Example'::*;
part def Vehicle;
part def FuelPump;
part def FuelTank;
part vehicle: Vehicle {
part tank: FuelTankAssembly {
port :>> fuelTankPort {
out item :>> fuelSupply;
in item :>> fuelReturn;
}
bind fuelTankPort.fuelSupply = pump.pumpOut;
bind fuelTankPort.fuelReturn = tank.fuelIn;
part pump: FuelPump {
out item pumpOut: Fuel;
in item pumpIn: Fuel;
}
part tank: FuelTank {
out item fuelOut: Fuel;
in item fuelIn: Fuel;
}
}
}
}
Loading
Loading