From 87d2017be3b262daea80ecc3853ea3d22d0c4143 Mon Sep 17 00:00:00 2001 From: Orhan Date: Mon, 8 May 2023 16:40:03 +0200 Subject: [PATCH 01/61] Added FTA Grammar --- extension/langium-config.json | 7 ++ extension/package.json | 17 ++++ extension/src-language-server/fta.langium | 40 ++++++++ .../fta/diagram-generatorFta.ts | 17 ++++ .../src-language-server/fta/fta-module.ts | 99 +++++++++++++++++++ .../src-language-server/fta/fta-validator.ts | 35 +++++++ extension/src-language-server/main.ts | 2 +- extension/src-language-server/module.ts | 28 +++++- extension/src/language-extension.ts | 18 +++- test.fta | 14 +++ teststpa.stpa | 6 ++ 11 files changed, 274 insertions(+), 9 deletions(-) create mode 100644 extension/src-language-server/fta.langium create mode 100644 extension/src-language-server/fta/diagram-generatorFta.ts create mode 100644 extension/src-language-server/fta/fta-module.ts create mode 100644 extension/src-language-server/fta/fta-validator.ts create mode 100644 test.fta create mode 100644 teststpa.stpa diff --git a/extension/langium-config.json b/extension/langium-config.json index 5695370..da85277 100644 --- a/extension/langium-config.json +++ b/extension/langium-config.json @@ -7,6 +7,13 @@ "textMate": { "out": "syntaxes/stpa.tmLanguage.json" } + },{ + "id": "fta" , + "grammar": "src-language-server/fta.langium", + "fileExtensions": [".fta"], + "textMate": { + "out": "syntaxes/fta.tmLanguage.json" + } }], "out": "src-language-server/generated" } diff --git a/extension/package.json b/extension/package.json index 5ccb5c7..878041f 100644 --- a/extension/package.json +++ b/extension/package.json @@ -29,6 +29,17 @@ ".stpa" ], "configuration": "./language-configuration.json" + }, + { + "id": "fta", + "aliases": [ + "fta", + "fta" + ], + "extensions": [ + ".fta" + ], + "configuration": "./language-configuration.json" } ], "grammars": [ @@ -36,6 +47,11 @@ "language": "stpa", "scopeName": "source.stpa", "path": "./syntaxes/stpa.tmLanguage.json" + }, + { + "language": "fta", + "scopeName": "source.fta", + "path": "./syntaxes/fta.tmLanguage.json" } ], "configuration": { @@ -304,6 +320,7 @@ }, "activationEvents": [ "onLanguage:stpa", + "onLanguage:fta", "onCommand:pasta.getLTLFormula" ], "files": [ diff --git a/extension/src-language-server/fta.langium b/extension/src-language-server/fta.langium new file mode 100644 index 0000000..b40ea21 --- /dev/null +++ b/extension/src-language-server/fta.langium @@ -0,0 +1,40 @@ +grammar Fta + +entry Model: + ('Components' components+=Component*)? + ('Gates' gates+=Gate*)? + ('TopEvent' topEvent=TopEvent); + +Component: + name=ID description=STRING; + +Gate: + name=ID ":" + (type=AndOrGate '[' componentOrGate+=ComponentOrGate (',' componentOrGate+=ComponentOrGate)+ ']' | + type=KNGate number=INT '[' componentOrGate+=ComponentOrGate (',' componentOrGate+=ComponentOrGate)+ ']' | + type=InhibitGate '[' componentOrGate+=ComponentOrGate ',' componentOrGate+=ComponentOrGate ']' | + componentOrGate+=ComponentOrGate type=(InhibitGate|AndOrGate) componentOrGate+=ComponentOrGate); + +TopEvent: + name=STRING ":" gate=[Gate:ID]; + +ComponentOrGate: + (component+=[Component:ID] | gate+=[Gate:ID]); + +AndOrGate returns string: + 'AND' | 'OR'; + +KNGate returns string: + 'k/N'; + +InhibitGate returns string: + 'INHIBIT'; + +hidden terminal WS: /\s+/; +terminal ID: /[_a-zA-Z][\w_]*/; +terminal INT returns number: /[0-9]+/; +terminal STRING: /"(\\.|[^"\\])*"|'(\\.|[^'\\])*'/; + +hidden terminal ML_COMMENT: /\/\*[\s\S]*?\*\//; +hidden terminal SL_COMMENT: /\/\/[^\n\r]*/; + diff --git a/extension/src-language-server/fta/diagram-generatorFta.ts b/extension/src-language-server/fta/diagram-generatorFta.ts new file mode 100644 index 0000000..d6949d2 --- /dev/null +++ b/extension/src-language-server/fta/diagram-generatorFta.ts @@ -0,0 +1,17 @@ +import { AstNode } from 'langium'; +import { GeneratorContext, LangiumDiagramGenerator } from 'langium-sprotty'; +import { SModelRoot, SLabel, SModelElement } from 'sprotty-protocol'; +import { isTopEvent, isGate, isComponent, Model } from '../generated/ast'; + + +export class FtaDiagramGenerator extends LangiumDiagramGenerator{ + + + protected generateRoot(args: GeneratorContext): SModelRoot { + + return { + type: 'graph', + id: 'root', + } + } +} \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts new file mode 100644 index 0000000..b708d08 --- /dev/null +++ b/extension/src-language-server/fta/fta-module.ts @@ -0,0 +1,99 @@ + +import ElkConstructor from 'elkjs/lib/elk.bundled'; +import {createDefaultModule, createDefaultSharedModule, DefaultSharedModuleContext, inject, Module, PartialLangiumServices} from 'langium'; +import {DefaultDiagramServerManager, DiagramActionNotification, LangiumSprottyServices, LangiumSprottySharedServices, SprottyDiagramServices, SprottySharedServices} from 'langium-sprotty'; +import { DefaultElementFilter, ElkFactory, ElkLayoutEngine, IElementFilter, ILayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; +//Change starting here later +import { StpaDiagramGenerator } from '../stpa/diagram-generator'; +import { StpaLayoutConfigurator } from '../stpa/layout-config'; +import { StpaDiagramServer } from '../stpa/stpa-diagramServer'; +import { StpaScopeProvider } from '../stpa/stpa-scopeProvider'; +//till here +import { FtaValidator, FtaValidationRegistry } from './fta-validator'; +import { URI } from 'vscode-uri'; +import { DiagramOptions } from 'sprotty-protocol'; +import { FtaGeneratedModule, StpaGeneratedSharedModule } from '../generated/module'; +//and these 3 again +import { StpaSynthesisOptions } from '../stpa/synthesis-options'; +import { ContextTableProvider } from '../stpa/contextTable/context-dataProvider'; +import { IDEnforcer } from '../stpa/ID-enforcer'; +import { FtaDiagramGenerator } from './diagram-generatorFta'; + + + + + + + + +export type FtaAddedServices = { + validation: { + FtaValidator: FtaValidator; + } + options: { + StpaSynthesisOptions: StpaSynthesisOptions + } +} + + +export type FtaServices = LangiumSprottyServices & FtaAddedServices + + +export const FtaModule: Module = { + diagram: { + DiagramGenerator: services => new FtaDiagramGenerator(services), + }, + validation: { + ValidationRegistry: services => new FtaValidationRegistry(services), + FtaValidator: () => new FtaValidator() + }, + options: { + StpaSynthesisOptions: () => new StpaSynthesisOptions() + }, +}; + +export const ftaDiagramServerFactory = + (services: LangiumSprottySharedServices): ((clientId: string, options?: DiagramOptions) => StpaDiagramServer) => { + const connection = services.lsp.Connection; + const serviceRegistry = services.ServiceRegistry; + return (clientId, options) => { + const sourceUri = options?.sourceUri; + if (!sourceUri) { + throw new Error("Missing 'sourceUri' option in request."); + } + const language = serviceRegistry.getServices(URI.parse(sourceUri as string)) as FtaServices; + if (!language.diagram) { + throw new Error(`The '${language.LanguageMetaData.languageId}' language does not support diagrams.`); + } + return new StpaDiagramServer(async action => { + connection?.sendNotification(DiagramActionNotification.type, { clientId, action }); + }, language.diagram, language.options.StpaSynthesisOptions, clientId); + }; + }; + +export const FtaSprottySharedModule: Module = { + diagram: { + diagramServerFactory: ftaDiagramServerFactory, + DiagramServerManager: services => new DefaultDiagramServerManager(services) + } +}; + +export function createFtaServices(context: DefaultSharedModuleContext): { + shared: LangiumSprottySharedServices, + fta: FtaServices +} { + const shared = inject( + createDefaultSharedModule(context), + StpaGeneratedSharedModule, + FtaSprottySharedModule + ); + const fta = inject( + createDefaultModule({ shared }), + FtaGeneratedModule, + FtaModule + ); + shared.ServiceRegistry.register(fta); + // FtaValidationRegistry; + return { shared, fta }; +} + diff --git a/extension/src-language-server/fta/fta-validator.ts b/extension/src-language-server/fta/fta-validator.ts new file mode 100644 index 0000000..f4c2b2a --- /dev/null +++ b/extension/src-language-server/fta/fta-validator.ts @@ -0,0 +1,35 @@ + +import { Reference, ValidationAcceptor, ValidationChecks, ValidationRegistry } from 'langium'; +import { StpaAstType, Model, Component, Gate, TopEvent} from '../generated/ast'; +import type { FtaServices } from './fta-module'; + + +export class FtaValidationRegistry extends ValidationRegistry { + constructor(services: FtaServices) { + super(services); + const validator = services.validation.FtaValidator; + const checks: ValidationChecks = { + Model: validator.checkModel, + }; + this.register(checks, validator); + } +} + + +export class FtaValidator { + + + checkModel(model: Model, accept: ValidationAcceptor): void{ + this.checkUniqueComponents(model, accept); + } + + checkUniqueComponents(model: Model, accept: ValidationAcceptor): void{ + const componentNames = new Set(); + model.components.forEach(c => { + if (componentNames.has(c.name)) { + accept('error', `Component has non-unique name '${c.name}'.`, {node: c, property: 'name'}); + } + componentNames.add(c.name); + }) + } +} diff --git a/extension/src-language-server/main.ts b/extension/src-language-server/main.ts index bb3935b..1204495 100644 --- a/extension/src-language-server/main.ts +++ b/extension/src-language-server/main.ts @@ -27,7 +27,7 @@ import { createServices } from './module'; const connection = createConnection(ProposedFeatures.all); // Inject the language services -const { shared, stpa } = createServices({ connection, ...NodeFileSystem }); +const { shared, stpa, fta} = createServices({ connection, ...NodeFileSystem }); // Start the language server with the language-specific services startLanguageServer(shared); diff --git a/extension/src-language-server/module.ts b/extension/src-language-server/module.ts index a6ce0b4..11d52d8 100644 --- a/extension/src-language-server/module.ts +++ b/extension/src-language-server/module.ts @@ -17,8 +17,10 @@ import { createDefaultModule, createDefaultSharedModule, DefaultSharedModuleContext, inject } from 'langium'; import { LangiumSprottySharedServices } from 'langium-sprotty'; -import { StpaGeneratedModule, StpaGeneratedSharedModule } from './generated/module'; +import { StpaGeneratedModule, StpaGeneratedSharedModule, FtaGeneratedModule} from './generated/module'; import { StpaServices, StpaSprottySharedModule, STPAModule } from './stpa/stpa-module'; +import{FtaModule, FtaSprottySharedModule, FtaServices} from './fta/fta-module'; + /** * Create the full set of services required by Langium. @@ -35,17 +37,33 @@ import { StpaServices, StpaSprottySharedModule, STPAModule } from './stpa/stpa-m * @param context Optional module context with the LSP connection * @returns An object wrapping the shared services and the language-specific services */ -export function createServices(context: DefaultSharedModuleContext): { shared: LangiumSprottySharedServices, stpa: StpaServices; } { +export function createServices(context: DefaultSharedModuleContext): { shared: LangiumSprottySharedServices, stpa: StpaServices, fta:FtaServices; } { const shared = inject( createDefaultSharedModule(context), StpaGeneratedSharedModule, - StpaSprottySharedModule + StpaSprottySharedModule, + FtaSprottySharedModule ); const stpa = inject( createDefaultModule({ shared }), StpaGeneratedModule, - STPAModule, + STPAModule + ); + const fta = inject( + createDefaultModule({ shared }), + FtaGeneratedModule, + FtaModule ); shared.ServiceRegistry.register(stpa); - return { shared, stpa }; + shared.ServiceRegistry.register(fta); + return { shared,stpa, fta}; } + + +/** + * Syntax highlighting from Fta does not work -> + * I tried adding stpa to my example project and there the later added dsl ,stpa, is not highlighted + * Here fta is not highlighted + * Has nothing to do with Sprotty, etc. + * The highlighting probably gets automatically generated for the first language but not for a language thats get added later + */ \ No newline at end of file diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index 2402f34..8a56c67 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -51,7 +51,7 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { }); // add auto formatting provider - const sel: vscode.DocumentSelector = { scheme: 'file', language: 'stpa' }; + const sel: vscode.DocumentSelector = { scheme: 'file', language: 'stpa' }; vscode.languages.registerDocumentFormattingEditProvider(sel, new StpaFormattingEditProvider()); // handling of notifications regarding the context table @@ -81,6 +81,8 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { }); } + + /** * Notifies the language server that a textdocument has changed. * @param changeEvent The change in the text document. @@ -261,12 +263,15 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } }; - const fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/*.stpa'); + const fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/*.(stpa|fta)'); context.subscriptions.push(fileSystemWatcher); // Options to control the language client const clientOptions: LanguageClientOptions = { - documentSelector: [{ scheme: 'file', language: 'stpa' }], + documentSelector: [ + { scheme: 'file', language: 'stpa' }, + { scheme: 'file', language: 'fta' } + ], synchronize: { // Notify the server about file changes to files contained in the workspace fileEvents: fileSystemWatcher @@ -280,9 +285,16 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { serverOptions, clientOptions ); + const languageClientFta = new LanguageClient( + 'fta', + 'fta', + serverOptions, + clientOptions + ); // Start the client. This will also launch the server languageClient.start(); + languageClientFta.start(); // diagram is updated when file changes fileSystemWatcher.onDidChange((uri) => this.updateViews(languageClient, uri.toString())); return languageClient; diff --git a/test.fta b/test.fta new file mode 100644 index 0000000..9680e4c --- /dev/null +++ b/test.fta @@ -0,0 +1,14 @@ +Components +M1 "Hello" +M2 "Hello too" +M3 "asd" + +Gates +G1:OR [M1,M2] +G2:AND [M1,M2,M3] +G3:INHIBIT [M1,M3] +G4:k/N 2 [M1,M2] +G5:M1 AND M2 + +TopEvent +"System Failure": G1 diff --git a/teststpa.stpa b/teststpa.stpa new file mode 100644 index 0000000..f996cfc --- /dev/null +++ b/teststpa.stpa @@ -0,0 +1,6 @@ +Losses +L1 "test" +L2 "FSDFA" + +Hazards + From 3b9be6d6d610394de975540abe8d5086533cb93e Mon Sep 17 00:00:00 2001 From: Orhan Date: Tue, 9 May 2023 12:33:09 +0200 Subject: [PATCH 02/61] #Fixed Grammar and some changes --- extension/src-language-server/fta.langium | 4 ++-- .../fta/diagram-generatorFta.ts | 7 +++---- .../src-language-server/fta/fta-module.ts | 18 ++++-------------- .../src-language-server/fta/fta-validator.ts | 10 +++++----- extension/src/language-extension.ts | 7 ------- 5 files changed, 14 insertions(+), 32 deletions(-) diff --git a/extension/src-language-server/fta.langium b/extension/src-language-server/fta.langium index b40ea21..d3f8ae9 100644 --- a/extension/src-language-server/fta.langium +++ b/extension/src-language-server/fta.langium @@ -1,6 +1,6 @@ grammar Fta -entry Model: +entry ModelFTA: ('Components' components+=Component*)? ('Gates' gates+=Gate*)? ('TopEvent' topEvent=TopEvent); @@ -33,7 +33,7 @@ InhibitGate returns string: hidden terminal WS: /\s+/; terminal ID: /[_a-zA-Z][\w_]*/; terminal INT returns number: /[0-9]+/; -terminal STRING: /"(\\.|[^"\\])*"|'(\\.|[^'\\])*'/; +terminal STRING: /"[^"]*"|'[^']*'/; hidden terminal ML_COMMENT: /\/\*[\s\S]*?\*\//; hidden terminal SL_COMMENT: /\/\/[^\n\r]*/; diff --git a/extension/src-language-server/fta/diagram-generatorFta.ts b/extension/src-language-server/fta/diagram-generatorFta.ts index d6949d2..cf8203b 100644 --- a/extension/src-language-server/fta/diagram-generatorFta.ts +++ b/extension/src-language-server/fta/diagram-generatorFta.ts @@ -1,13 +1,12 @@ -import { AstNode } from 'langium'; import { GeneratorContext, LangiumDiagramGenerator } from 'langium-sprotty'; -import { SModelRoot, SLabel, SModelElement } from 'sprotty-protocol'; -import { isTopEvent, isGate, isComponent, Model } from '../generated/ast'; +import { SModelRoot } from 'sprotty-protocol'; +import { ModelFTA } from '../generated/ast'; export class FtaDiagramGenerator extends LangiumDiagramGenerator{ - protected generateRoot(args: GeneratorContext): SModelRoot { + protected generateRoot(args: GeneratorContext): SModelRoot { return { type: 'graph', diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index b708d08..0fb8856 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -1,22 +1,12 @@ -import ElkConstructor from 'elkjs/lib/elk.bundled'; -import {createDefaultModule, createDefaultSharedModule, DefaultSharedModuleContext, inject, Module, PartialLangiumServices} from 'langium'; -import {DefaultDiagramServerManager, DiagramActionNotification, LangiumSprottyServices, LangiumSprottySharedServices, SprottyDiagramServices, SprottySharedServices} from 'langium-sprotty'; -import { DefaultElementFilter, ElkFactory, ElkLayoutEngine, IElementFilter, ILayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; -//Change starting here later -import { StpaDiagramGenerator } from '../stpa/diagram-generator'; -import { StpaLayoutConfigurator } from '../stpa/layout-config'; +import { createDefaultModule, createDefaultSharedModule, DefaultSharedModuleContext, inject, Module, PartialLangiumServices } from 'langium'; +import { DefaultDiagramServerManager, DiagramActionNotification, LangiumSprottyServices, LangiumSprottySharedServices, SprottyDiagramServices, SprottySharedServices } from 'langium-sprotty'; import { StpaDiagramServer } from '../stpa/stpa-diagramServer'; -import { StpaScopeProvider } from '../stpa/stpa-scopeProvider'; -//till here -import { FtaValidator, FtaValidationRegistry } from './fta-validator'; -import { URI } from 'vscode-uri'; import { DiagramOptions } from 'sprotty-protocol'; +import { URI } from 'vscode-uri'; import { FtaGeneratedModule, StpaGeneratedSharedModule } from '../generated/module'; -//and these 3 again +import { FtaValidationRegistry, FtaValidator } from './fta-validator'; import { StpaSynthesisOptions } from '../stpa/synthesis-options'; -import { ContextTableProvider } from '../stpa/contextTable/context-dataProvider'; -import { IDEnforcer } from '../stpa/ID-enforcer'; import { FtaDiagramGenerator } from './diagram-generatorFta'; diff --git a/extension/src-language-server/fta/fta-validator.ts b/extension/src-language-server/fta/fta-validator.ts index f4c2b2a..a9d08d5 100644 --- a/extension/src-language-server/fta/fta-validator.ts +++ b/extension/src-language-server/fta/fta-validator.ts @@ -1,6 +1,6 @@ -import { Reference, ValidationAcceptor, ValidationChecks, ValidationRegistry } from 'langium'; -import { StpaAstType, Model, Component, Gate, TopEvent} from '../generated/ast'; +import { ValidationAcceptor, ValidationChecks, ValidationRegistry } from 'langium'; +import { ModelFTA, StpaAstType } from '../generated/ast'; import type { FtaServices } from './fta-module'; @@ -9,7 +9,7 @@ export class FtaValidationRegistry extends ValidationRegistry { super(services); const validator = services.validation.FtaValidator; const checks: ValidationChecks = { - Model: validator.checkModel, + ModelFTA: validator.checkModel, }; this.register(checks, validator); } @@ -19,11 +19,11 @@ export class FtaValidationRegistry extends ValidationRegistry { export class FtaValidator { - checkModel(model: Model, accept: ValidationAcceptor): void{ + checkModel(model: ModelFTA, accept: ValidationAcceptor): void{ this.checkUniqueComponents(model, accept); } - checkUniqueComponents(model: Model, accept: ValidationAcceptor): void{ + checkUniqueComponents(model: ModelFTA, accept: ValidationAcceptor): void{ const componentNames = new Set(); model.components.forEach(c => { if (componentNames.has(c.name)) { diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index 8a56c67..4de04d2 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -285,16 +285,9 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { serverOptions, clientOptions ); - const languageClientFta = new LanguageClient( - 'fta', - 'fta', - serverOptions, - clientOptions - ); // Start the client. This will also launch the server languageClient.start(); - languageClientFta.start(); // diagram is updated when file changes fileSystemWatcher.onDidChange((uri) => this.updateViews(languageClient, uri.toString())); return languageClient; From bd5af72774e4fc62a932d2f28b6dab7bc5ec5264 Mon Sep 17 00:00:00 2001 From: Orhan Date: Tue, 9 May 2023 14:44:01 +0200 Subject: [PATCH 03/61] Added Conditions to Grammar and fixed behavior --- extension/src-language-server/fta.langium | 16 ++++++----- .../src-language-server/fta/fta-validator.ts | 10 +++++++ test.fta | 27 +++++++++++++------ 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/extension/src-language-server/fta.langium b/extension/src-language-server/fta.langium index d3f8ae9..4e289f5 100644 --- a/extension/src-language-server/fta.langium +++ b/extension/src-language-server/fta.langium @@ -1,25 +1,29 @@ grammar Fta entry ModelFTA: - ('Components' components+=Component*)? + ('Components' components+=Component*)? + ('Conditions' condition+=Condition)? ('Gates' gates+=Gate*)? ('TopEvent' topEvent=TopEvent); Component: name=ID description=STRING; +Condition: + name=ID description=STRING; Gate: name=ID ":" - (type=AndOrGate '[' componentOrGate+=ComponentOrGate (',' componentOrGate+=ComponentOrGate)+ ']' | - type=KNGate number=INT '[' componentOrGate+=ComponentOrGate (',' componentOrGate+=ComponentOrGate)+ ']' | - type=InhibitGate '[' componentOrGate+=ComponentOrGate ',' componentOrGate+=ComponentOrGate ']' | - componentOrGate+=ComponentOrGate type=(InhibitGate|AndOrGate) componentOrGate+=ComponentOrGate); + (type=AndOrGate '[' componentOrGate+=[ComponentOrGate:ID] (',' componentOrGate+=[ComponentOrGate:ID])+ ']' | + type=KNGate number=INT '[' componentOrGate+=[ComponentOrGate:ID] (',' componentOrGate+=[ComponentOrGate:ID])+ ']' | + type=InhibitGate '[' componentOrGate+=[ComponentOrGate:ID] ',' condition+=[Condition:ID] ']' | + componentOrGate+=[ComponentOrGate:ID] type=AndOrGate componentOrGate+=[ComponentOrGate:ID] | + componentOrGate+=[ComponentOrGate:ID] type=InhibitGate condition+=[Condition:ID]); TopEvent: name=STRING ":" gate=[Gate:ID]; ComponentOrGate: - (component+=[Component:ID] | gate+=[Gate:ID]); + Gate | Component; AndOrGate returns string: 'AND' | 'OR'; diff --git a/extension/src-language-server/fta/fta-validator.ts b/extension/src-language-server/fta/fta-validator.ts index a9d08d5..ec80cbd 100644 --- a/extension/src-language-server/fta/fta-validator.ts +++ b/extension/src-language-server/fta/fta-validator.ts @@ -21,6 +21,7 @@ export class FtaValidator { checkModel(model: ModelFTA, accept: ValidationAcceptor): void{ this.checkUniqueComponents(model, accept); + this.checkUniqueGates(model, accept); } checkUniqueComponents(model: ModelFTA, accept: ValidationAcceptor): void{ @@ -32,4 +33,13 @@ export class FtaValidator { componentNames.add(c.name); }) } + checkUniqueGates(model:ModelFTA, accept:ValidationAcceptor): void{ + const gateNames = new Set(); + model.gates.forEach(g => { + if (gateNames.has(g.name)) { + accept('error', `Gate has non-unique name '${g.name}'.`, {node: g, property: 'name'}); + } + gateNames.add(g.name); + }) + } } diff --git a/test.fta b/test.fta index 9680e4c..6fb484b 100644 --- a/test.fta +++ b/test.fta @@ -1,14 +1,25 @@ Components -M1 "Hello" -M2 "Hello too" -M3 "asd" +M1 "Redundant memory unit 1" +M2 "Redundant memory unit 2" +M3 "Redundant memory unit 3" +C1 "CPU1" +C2 "CPU2" +PS "Power supply" +B "System bus" + +Conditions +U "In Use" Gates -G1:OR [M1,M2] -G2:AND [M1,M2,M3] -G3:INHIBIT [M1,M3] -G4:k/N 2 [M1,M2] -G5:M1 AND M2 +G6:k/N 2 [M1,M2,M3] +G5:OR [C2,PS,G6] +G4:OR [C1,PS,G6] +G3:AND [G4,G5] +//G3: G4 AND G5 //Alternatively +G2:OR [G3,B] +//G2: G3 OR B +G1:INHIBIT [G2,U] +//G1:G2 INHIBIT U TopEvent "System Failure": G1 From 320f99eeb4d34ac50e205c414dbc914a35e2a89a Mon Sep 17 00:00:00 2001 From: Orhan Date: Tue, 9 May 2023 14:59:46 +0200 Subject: [PATCH 04/61] test.fta changed --- test.fta | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test.fta b/test.fta index 6fb484b..23f6f01 100644 --- a/test.fta +++ b/test.fta @@ -11,15 +11,15 @@ Conditions U "In Use" Gates -G6:k/N 2 [M1,M2,M3] -G5:OR [C2,PS,G6] -G4:OR [C1,PS,G6] -G3:AND [G4,G5] -//G3: G4 AND G5 //Alternatively -G2:OR [G3,B] -//G2: G3 OR B G1:INHIBIT [G2,U] //G1:G2 INHIBIT U +G2:OR [G3,B] +//G2: G3 OR B +G3:AND [G4,G5] +//G3: G4 AND G5 //Alternatively +G4:OR [C1,PS,G6] +G5:OR [C2,PS,G6] +G6:k/N 2 [M1,M2,M3] TopEvent "System Failure": G1 From b1ccb1ce1c68eec801e5bd378304af6d12cf01c6 Mon Sep 17 00:00:00 2001 From: Orhan Date: Tue, 16 May 2023 16:47:51 +0200 Subject: [PATCH 05/61] Updated Grammar and diagram generator --- extension/src-language-server/fta.langium | 32 +-- .../fta/diagram-generatorFta.ts | 16 -- .../fta/fta-diagram-generator.ts | 193 ++++++++++++++++++ .../fta/fta-diagramServer.ts | 100 +++++++++ .../src-language-server/fta/fta-interfaces.ts | 22 ++ .../src-language-server/fta/fta-model.ts | 18 ++ .../src-language-server/fta/fta-module.ts | 57 ++++-- .../fta/fta-synthesis-options.ts | 14 ++ .../src-language-server/fta/fta-utils.ts | 149 ++++++++++++++ .../src-language-server/fta/fta-validator.ts | 17 +- extension/src-language-server/handler.ts | 1 + extension/src-language-server/module.ts | 9 - extension/src-language-server/utils.ts | 12 +- extension/src/language-extension.ts | 3 + test.fta | 2 +- teststpa.stpa | 1 + 16 files changed, 584 insertions(+), 62 deletions(-) delete mode 100644 extension/src-language-server/fta/diagram-generatorFta.ts create mode 100644 extension/src-language-server/fta/fta-diagram-generator.ts create mode 100644 extension/src-language-server/fta/fta-diagramServer.ts create mode 100644 extension/src-language-server/fta/fta-interfaces.ts create mode 100644 extension/src-language-server/fta/fta-model.ts create mode 100644 extension/src-language-server/fta/fta-synthesis-options.ts create mode 100644 extension/src-language-server/fta/fta-utils.ts diff --git a/extension/src-language-server/fta.langium b/extension/src-language-server/fta.langium index 4e289f5..7aee623 100644 --- a/extension/src-language-server/fta.langium +++ b/extension/src-language-server/fta.langium @@ -2,7 +2,7 @@ grammar Fta entry ModelFTA: ('Components' components+=Component*)? - ('Conditions' condition+=Condition)? + ('Conditions' conditions+=Condition)? ('Gates' gates+=Gate*)? ('TopEvent' topEvent=TopEvent); @@ -11,28 +11,30 @@ Component: Condition: name=ID description=STRING; + Gate: - name=ID ":" - (type=AndOrGate '[' componentOrGate+=[ComponentOrGate:ID] (',' componentOrGate+=[ComponentOrGate:ID])+ ']' | - type=KNGate number=INT '[' componentOrGate+=[ComponentOrGate:ID] (',' componentOrGate+=[ComponentOrGate:ID])+ ']' | - type=InhibitGate '[' componentOrGate+=[ComponentOrGate:ID] ',' condition+=[Condition:ID] ']' | - componentOrGate+=[ComponentOrGate:ID] type=AndOrGate componentOrGate+=[ComponentOrGate:ID] | - componentOrGate+=[ComponentOrGate:ID] type=InhibitGate condition+=[Condition:ID]); + name=ID ":" type=(AND|OR|KNGate|InhibitGate); TopEvent: - name=STRING ":" gate=[Gate:ID]; + name=STRING ":" child+=[Child:ID]; -ComponentOrGate: +Child: Gate | Component; -AndOrGate returns string: - 'AND' | 'OR'; +AND: + (string='AND' '[' child+=[Child:ID] (',' child+=[Child:ID])+ ']' | + child+=[Child:ID] string='AND' child+=[Child:ID]); + +OR: + (string='OR' '[' child+=[Child:ID] (',' child+=[Child:ID])+ ']' | + child+=[Child:ID] string='OR' child+=[Child:ID]); -KNGate returns string: - 'k/N'; +KNGate: + string='k/N' k=INT '[' child+=[Child:ID] (',' child+=[Child:ID])+ ']'; -InhibitGate returns string: - 'INHIBIT'; +InhibitGate: + (string='INHIBIT' '[' child+=[Child:ID] ',' condition+=[Condition:ID] ']'| + child+=[Child:ID] string='INHIBIT' condition+=[Condition:ID]); hidden terminal WS: /\s+/; terminal ID: /[_a-zA-Z][\w_]*/; diff --git a/extension/src-language-server/fta/diagram-generatorFta.ts b/extension/src-language-server/fta/diagram-generatorFta.ts deleted file mode 100644 index cf8203b..0000000 --- a/extension/src-language-server/fta/diagram-generatorFta.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { GeneratorContext, LangiumDiagramGenerator } from 'langium-sprotty'; -import { SModelRoot } from 'sprotty-protocol'; -import { ModelFTA } from '../generated/ast'; - - -export class FtaDiagramGenerator extends LangiumDiagramGenerator{ - - - protected generateRoot(args: GeneratorContext): SModelRoot { - - return { - type: 'graph', - id: 'root', - } - } -} \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-diagram-generator.ts b/extension/src-language-server/fta/fta-diagram-generator.ts new file mode 100644 index 0000000..6cafcb9 --- /dev/null +++ b/extension/src-language-server/fta/fta-diagram-generator.ts @@ -0,0 +1,193 @@ +import { AstNode } from 'langium'; +import { GeneratorContext, LangiumDiagramGenerator } from 'langium-sprotty'; +import { SModelRoot, SLabel, SModelElement } from 'sprotty-protocol'; +import { ModelFTA, isAND, isCommand, isComponent, isCondition, isGate, isInhibitGate, isKNGate, isOR, isTopEvent, AND } from '../generated/ast'; +import { FtaServices } from './fta-module'; +import { filterModel } from '../stpa/filtering'; +import { FTAEdge, FTANode } from './fta-interfaces'; +import { getAndGates, getAspect, getInhibitGates, getOrGates, getTargets, getkNGates, setLevelsForFTANodes } from './fta-utils'; +import { FTA_EDGE_TYPE, FTA_NODE_TYPE, PARENT_TYPE } from './fta-model'; + + +export class FtaDiagramGenerator extends LangiumDiagramGenerator{ + + constructor(services: FtaServices){ + super(services); + } + + /** + * Generates a SGraph for the FTA model contained in {@code args}. + * @param args GeneratorContext for the FTA model. + * @returns the root of the generated SGraph. + */ + protected generateRoot(args: GeneratorContext): SModelRoot { + const { document } = args; + const model: ModelFTA = document.parseResult.value; + //set filter for later maybe + const filteredModelFTA = model; + + // let ftaChildren: SModelElement[] = this.generateAspectWithEdges(filteredModelFTA.topEvent, args); + // Components first -> Gates -> Conditions -> Top Event + let ftaChildren: SModelElement[] = filteredModelFTA.components?.map(l => this.generateFTANode(l, args)); + const andGates = getAndGates(filteredModelFTA.gates); //all gates not listed in debug + const orGates = getOrGates(filteredModelFTA.gates); //aswell as edges missing + const kNGates = getkNGates(filteredModelFTA.gates); + const inhibitGates = getInhibitGates(filteredModelFTA.gates); + ftaChildren = ftaChildren.concat([ + ...filteredModelFTA.conditions?.map(c => this.generateAspectWithEdges(c,args)).flat(1), + //...filteredModelFTA.gates?.map(g => this.generateAspectWithEdges(g, args)).flat(1), + ...andGates?.map(a => this.generateAspectWithEdges(a, args)).flat(1), + ...orGates?.map(o => this.generateAspectWithEdges(o, args)).flat(1), + ...kNGates?.map(k => this.generateAspectWithEdges(k, args)).flat(1), + ...inhibitGates?.map(i => this.generateAspectWithEdges(i, args)).flat(1), + ...this.generateAspectWithEdges(filteredModelFTA.topEvent, args), + + ]); + + + // filtering the nodes of the FTA graph + const ftaNodes: FTANode[] = []; + for (const node of ftaChildren) { + if (node.type === FTA_NODE_TYPE) { + ftaNodes.push(node as FTANode); + } + } + + // give the top event the level 0 + setLevelsForFTANodes(ftaNodes); + + return { + type: 'graph', + id: 'root', + children: [ + { + type: PARENT_TYPE, + id: 'relationships', + children: ftaChildren + } + ] + }; + + } + + /** + * Generates a node and the edges for the given {@code node}. + * @param node FTA component for which a node and edges should be generated. + * @param args GeneratorContext of the FTA model. + * @returns A node representing {@code node} and edges representing the references {@code node} contains. + */ + private generateAspectWithEdges(node: AstNode, args: GeneratorContext): SModelElement[] { + // node must be created first in order to access the id when creating the edges + const ftaNode = this.generateFTANode(node, args); + const elements: SModelElement[] = this.generateEdgesForFTANode(node, args); + elements.push(ftaNode); + return elements; + } + + /** + * Generates the edges for {@code node}. + * @param node FTA component for which the edges should be created. + * @param args GeneratorContext of the FTA model. + * @returns Edges representing the references {@code node} contains. + */ + private generateEdgesForFTANode(node: AstNode, args: GeneratorContext): SModelElement[] { + const idCache = args.idCache; + const elements: SModelElement[] = []; + const sourceId = idCache.getId(node); + // for every reference an edge is created + const targets = getTargets(node); + for (const target of targets) { + const targetId = idCache.getId(target); + const edgeId = idCache.uniqueId(`${sourceId}:-:${targetId}`, undefined); + if (sourceId && targetId) { + const e = this.generateFTAEdge(edgeId, sourceId, targetId, '', args); + elements.push(e); + } + } + return elements; + + } + + /** + * Generates a single FTAEdge based on the given arguments. + * @param edgeId The ID of the edge that should be created. + * @param sourceId The ID of the source of the edge. + * @param targetId The ID of the target of the edge. + * @param label The label of the edge. + * @param param4 GeneratorContext of the FTA model. + * @returns An FTAEdge. + */ + private generateFTAEdge(edgeId: string, sourceId: string, targetId: string, label: string, { idCache }: GeneratorContext): FTAEdge { + let children: SModelElement[] = []; + if (label !== '') { + children = [ + { + type: 'label:xref', + id: idCache.uniqueId(edgeId + '.label'), + text: label + } + ]; + } + return { + type: FTA_EDGE_TYPE, + id: edgeId, + sourceId: sourceId, + targetId: targetId, + children: children + }; + } + + /** + * Generates a single FTANode for the given {@code node}. + * @param node The FTA component the node should be created for. + * @param args GeneratorContext of the FTA model. + * @returns A FTANode representing {@code node}. + */ + private generateFTANode(node: AstNode, args: GeneratorContext): FTANode { + const idCache = args.idCache; + if(isTopEvent(node) || isGate(node) || isComponent(node) || isCondition(node)){ + const nodeId = idCache.uniqueId(node.name, node); + + let children: SModelElement[] = [ + { + type: 'label', + id: idCache.uniqueId(nodeId + '.label'), + text: node.name + } + ]; + if(isComponent(node) || isCondition(node)){ + return { + type: FTA_NODE_TYPE, + id: nodeId, + aspect: getAspect(node), + description: node.description, + children: children, + layout: 'stack', + layoutOptions: { + paddingTop: 10.0, + paddingBottom: 10.0, + paddngLeft: 10.0, + paddingRight: 10.0 + } + }; + }else{ + return { + type: FTA_NODE_TYPE, + id: nodeId, + aspect: getAspect(node), + description: "", + children: children, + layout: 'stack', + layoutOptions: { + paddingTop: 10.0, + paddingBottom: 10.0, + paddngLeft: 10.0, + paddingRight: 10.0 + } + }; + } + }else { + throw new Error("generateFTANode method should only be called with an FTA component"); + } + } +} \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-diagramServer.ts b/extension/src-language-server/fta/fta-diagramServer.ts new file mode 100644 index 0000000..ededc42 --- /dev/null +++ b/extension/src-language-server/fta/fta-diagramServer.ts @@ -0,0 +1,100 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2022 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Action, DiagramServices, DiagramServer, RequestAction, RequestModelAction, ResponseAction } from 'sprotty-protocol'; +import { UpdateViewAction } from '../actions'; +import { SetSynthesisOptionsAction, UpdateOptionsAction } from '../options/actions'; +import { DropDownOption } from '../options/option-models'; +import { FtaSynthesisOptions } from './fta-synthesis-options'; + +export class FtaDiagramServer extends DiagramServer { + + protected ftaOptions: FtaSynthesisOptions; + clientId: string; + + constructor(dispatch: (action: A) => Promise, + services: DiagramServices, clientId: string) { + super(dispatch, services); + this.clientId = clientId; + } + + accept(action: Action): Promise { + console.log("received from client: " + action.kind); + return super.accept(action); + } + + request(action: RequestAction): Promise { + console.log("request send from server to client: " + action.kind); + return super.request(action); + } + + protected handleAction(action: Action): Promise { + switch (action.kind) { + // case SetSynthesisOptionsAction.KIND: + // return this.handleSetSynthesisOption(action as SetSynthesisOptionsAction); + case UpdateViewAction.KIND: + return this.handleUpdateView(action as UpdateViewAction); + } + return super.handleAction(action); + } + + /* + protected handleSetSynthesisOption(action: SetSynthesisOptionsAction): Promise { + for (const option of action.options) { + const opt = this.ftaOptions.getSynthesisOptions().find(synOpt => synOpt.synthesisOption.id === option.id); + if (opt) { + opt.currentValue = option.currentValue; + // for dropdown menu options more must be done + if ((opt.synthesisOption as DropDownOption).currentId) { + (opt.synthesisOption as DropDownOption).currentId = option.currentValue; + this.dispatch({ kind: UpdateOptionsAction.KIND, valuedSynthesisOptions: this.ftaOptions.getSynthesisOptions(), clientId: this.clientId }); + } + } + } + const updateAction = { + kind: UpdateViewAction.KIND, + options: this.state.options + } as UpdateViewAction; + this.handleUpdateView(updateAction); + return Promise.resolve(); + } + */ + + protected async handleUpdateView(action: UpdateViewAction): Promise { + this.state.options = action.options; + try { + const newRoot = await this.diagramGenerator.generate({ + options: this.state.options ?? {}, + state: this.state + }); + newRoot.revision = ++this.state.revision; + this.state.currentRoot = newRoot; + await this.submitModel(this.state.currentRoot, true, action); + // ensures the the filterUCA option is correct + this.dispatch({ kind: UpdateOptionsAction.KIND, clientId: this.clientId }); + } catch (err) { + this.rejectRemoteRequest(action, err as Error); + console.error('Failed to generate diagram:', err); + } + } + + protected async handleRequestModel(action: RequestModelAction): Promise { + await super.handleRequestModel(action); + this.dispatch({ kind: UpdateOptionsAction.KIND, clientId: this.clientId }); + } + +} \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-interfaces.ts b/extension/src-language-server/fta/fta-interfaces.ts new file mode 100644 index 0000000..769ef87 --- /dev/null +++ b/extension/src-language-server/fta/fta-interfaces.ts @@ -0,0 +1,22 @@ +import { SNode, SEdge } from "sprotty-protocol"; +import{FTAAspect} from "./fta-model"; +import { Condition } from "../generated/ast"; + + +/** + * Node representing a FTA component. + */ +export interface FTANode extends SNode{ + aspect: FTAAspect, + description: string + highlight?: boolean + level?: number + +} + +/** + * Edge representing an edge in the relationship graph. + */ +export interface FTAEdge extends SEdge { + highlight?: boolean +} \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-model.ts b/extension/src-language-server/fta/fta-model.ts new file mode 100644 index 0000000..220f3e6 --- /dev/null +++ b/extension/src-language-server/fta/fta-model.ts @@ -0,0 +1,18 @@ + +//diagram elements +export const FTA_NODE_TYPE = 'node:fta'; +export const PARENT_TYPE= 'node:parent'; +export const EDGE_TYPE = 'edge'; +export const FTA_EDGE_TYPE = 'edge:fta'; + + +/** + * The different aspects of FTA. + */ +export enum FTAAspect { + COMPONENT, + CONDITION, + GATE, + TOPEVENT, + UNDEFINED +} \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index 0fb8856..31c3a16 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -7,8 +7,9 @@ import { URI } from 'vscode-uri'; import { FtaGeneratedModule, StpaGeneratedSharedModule } from '../generated/module'; import { FtaValidationRegistry, FtaValidator } from './fta-validator'; import { StpaSynthesisOptions } from '../stpa/synthesis-options'; -import { FtaDiagramGenerator } from './diagram-generatorFta'; - +import { FtaDiagramGenerator } from './fta-diagram-generator'; +import { FtaSynthesisOptions } from './fta-synthesis-options'; +import { FtaDiagramServer } from './fta-diagramServer'; @@ -16,19 +17,29 @@ import { FtaDiagramGenerator } from './diagram-generatorFta'; +/** + * Declaration of custom services - add your own service classes here. + */ export type FtaAddedServices = { validation: { FtaValidator: FtaValidator; } - options: { - StpaSynthesisOptions: StpaSynthesisOptions - } + //options: { + // FtaSynthesisOptions: FtaSynthesisOptions + // } } - +/** + * Union of Langium default services and your custom services - use this as constructor parameter + * of custom service classes. + */ export type FtaServices = LangiumSprottyServices & FtaAddedServices - +/** + * Dependency injection module that overrides Langium default services and contributes the + * declared custom services. The Langium defaults can be partially specified to override only + * selected services, while the custom services must be fully specified. + */ export const FtaModule: Module = { diagram: { DiagramGenerator: services => new FtaDiagramGenerator(services), @@ -37,13 +48,13 @@ export const FtaModule: Module new FtaValidationRegistry(services), FtaValidator: () => new FtaValidator() }, - options: { - StpaSynthesisOptions: () => new StpaSynthesisOptions() - }, + // options: { + // FtaSynthesisOptions: () => new FtaSynthesisOptions() + // }, }; export const ftaDiagramServerFactory = - (services: LangiumSprottySharedServices): ((clientId: string, options?: DiagramOptions) => StpaDiagramServer) => { + (services: LangiumSprottySharedServices): ((clientId: string, options?: DiagramOptions) => FtaDiagramServer) => { const connection = services.lsp.Connection; const serviceRegistry = services.ServiceRegistry; return (clientId, options) => { @@ -55,19 +66,35 @@ export const ftaDiagramServerFactory = if (!language.diagram) { throw new Error(`The '${language.LanguageMetaData.languageId}' language does not support diagrams.`); } - return new StpaDiagramServer(async action => { + return new FtaDiagramServer(async action => { connection?.sendNotification(DiagramActionNotification.type, { clientId, action }); - }, language.diagram, language.options.StpaSynthesisOptions, clientId); + }, language.diagram, clientId); }; }; - +/** + * instead of the default diagram server the fta-diagram server is used + */ export const FtaSprottySharedModule: Module = { diagram: { diagramServerFactory: ftaDiagramServerFactory, DiagramServerManager: services => new DefaultDiagramServerManager(services) } }; - +/** + * Create the full set of services required by Langium. + * + * First inject the shared services by merging two modules: + * - Langium default shared services + * - Services generated by langium-cli + * + * Then inject the language-specific services by merging three modules: + * - Langium default language-specific services + * - Services generated by langium-cli + * - Services specified in this file + * + * @param context Optional module context with the LSP connection + * @returns An object wrapping the shared services and the language-specific services + */ export function createFtaServices(context: DefaultSharedModuleContext): { shared: LangiumSprottySharedServices, fta: FtaServices diff --git a/extension/src-language-server/fta/fta-synthesis-options.ts b/extension/src-language-server/fta/fta-synthesis-options.ts new file mode 100644 index 0000000..58176eb --- /dev/null +++ b/extension/src-language-server/fta/fta-synthesis-options.ts @@ -0,0 +1,14 @@ +import { ValuedSynthesisOption } from "../options/option-models"; + +export class FtaSynthesisOptions { + + private options: ValuedSynthesisOption[]; + + constructor() { + } + + getSynthesisOptions(): ValuedSynthesisOption[] { + return []; + } + +} \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-utils.ts b/extension/src-language-server/fta/fta-utils.ts new file mode 100644 index 0000000..2192afb --- /dev/null +++ b/extension/src-language-server/fta/fta-utils.ts @@ -0,0 +1,149 @@ +import { AstNode } from 'langium'; +import { AND, InhibitGate, KNGate, OR, TopEvent, isAND, isComponent, isGate, isInhibitGate, isKNGate, isOR, isTopEvent, Child, isCondition, Gate } from '../generated/ast'; +import { FTANode } from './fta-interfaces'; +import { FTAAspect } from './fta-model'; + + + + + +/** + * Getter for the references contained in {@code node}. + * @param node The FTAAspect which tracings should be returned. + * @returns The objects {@code node} is traceable to. + */ +export function getTargets(node: AstNode): AstNode[] { + if (node) { + const targets: AstNode[] = []; + if(isTopEvent(node)){ //probably wrong + for (const ref of node.child) { + if (ref?.ref) { targets.push(ref.ref); } + } + }else if(isGate(node)){ + if(isAND(node.$type) || isOR(node.$type) || isKNGate(node.$type) || isInhibitGate(node.$type) ){ + for(const ref of node.$type.child){ + if(ref?.ref){targets.push(ref.ref);} + } + } + if(isInhibitGate(node.$type)){ + for(const ref of node.$type.condition){ + if(ref?.ref){targets.push(ref.ref);} + } + } + } + return targets; + }else{ + return []; + } +} + + +/** + * Getter for the aspect of a FTA component. + * @param node AstNode which aspect should determined. + * @returns the aspect of {@code node}. + */ +export function getAspect(node: AstNode): FTAAspect { + if (isComponent(node)) { + return FTAAspect.COMPONENT; + } else if (isGate(node)) { + return FTAAspect.GATE; + } else if (isTopEvent(node)) { + return FTAAspect.TOPEVENT; + } + return FTAAspect.UNDEFINED; +} + + + +/** + * Determines the layer {@code node} should be in depending on the FTA aspect it represents. + * @param node FTANode for which the layer should be determined. + * @param hazardDepth Maximal depth of the hazard hierarchy. + * @param sysConsDepth Maximal depth of the system-level constraint hierarchy. + * @returns The number of the layer {@code node} should be in. + */ +function determineLayerForFTANode(node: FTANode): number { + switch (node.aspect) { + case FTAAspect.TOPEVENT: + return 0; + default: + return -1; + } +} + +export function getAndGates(everyGate: Gate[]): AstNode[]{ + let result = []; + for(const gate of everyGate){ + if(isAND(gate)){ + result.push(gate); + } + } + + return result; +} +export function getOrGates(everyGate: Gate[]): AstNode[]{ + let result = []; + for(const gate of everyGate){ + if(isOR(gate)){ + result.push(gate); + } + } + + return result; +} +export function getkNGates(everyGate: Gate[]): AstNode[]{ + let result = []; + for(const gate of everyGate){ + if(isKNGate(gate)){ + result.push(gate); + } + } + + return result; +} +export function getInhibitGates(everyGate: Gate[]): AstNode[]{ + let result = []; + for(const gate of everyGate){ + if(isInhibitGate(gate)){ + result.push(gate); + } + } + + return result; +} + +/* +function setLevels(current: FTANode[], lvl: number, allNodes: FTANode[]): void{ + var next:FTANode[] = []; + for(const currentNode of current){ + currentNode.level = lvl; + if(isAND(currentNode) || isOR(currentNode) || isKNGate(currentNode) || isInhibitGate(currentNode) || isTopEvent(currentNode)){ + for(const child of currentNode.child){ + if(isAND(child) || isOR(child) || isKNGate(child) || isInhibitGate(child) || isComponent(child)){ + if(allNodes.includes(child)){ //Finde + next.push(child); + } + } + } + } + } + + setLevels(next, lvl++, allNodes); +} +*/ + + +/** + * Sets the level property for {@code nodes} depending on the layer they should be in. + * @param nodes The nodes representing the stpa components. + */ +export function setLevelsForFTANodes(nodes: FTANode[]): void{ + + for(const node of nodes){ + const level = determineLayerForFTANode(node); + node.level = level; + } + +} + diff --git a/extension/src-language-server/fta/fta-validator.ts b/extension/src-language-server/fta/fta-validator.ts index ec80cbd..936aa83 100644 --- a/extension/src-language-server/fta/fta-validator.ts +++ b/extension/src-language-server/fta/fta-validator.ts @@ -3,7 +3,9 @@ import { ValidationAcceptor, ValidationChecks, ValidationRegistry } from 'langiu import { ModelFTA, StpaAstType } from '../generated/ast'; import type { FtaServices } from './fta-module'; - +/** + * Registry for validation checks. + */ export class FtaValidationRegistry extends ValidationRegistry { constructor(services: FtaServices) { super(services); @@ -15,15 +17,21 @@ export class FtaValidationRegistry extends ValidationRegistry { } } - +/** + * Implementation of custom validations. + */ export class FtaValidator { - + /** + * Executes validation checks for the whole model. + * @param model The model to validate. + * @param accept + */ checkModel(model: ModelFTA, accept: ValidationAcceptor): void{ this.checkUniqueComponents(model, accept); this.checkUniqueGates(model, accept); } - + //prevent multiple components from having the same identifier. checkUniqueComponents(model: ModelFTA, accept: ValidationAcceptor): void{ const componentNames = new Set(); model.components.forEach(c => { @@ -33,6 +41,7 @@ export class FtaValidator { componentNames.add(c.name); }) } + //prevent multiple gates from having the same identifier. checkUniqueGates(model:ModelFTA, accept:ValidationAcceptor): void{ const gateNames = new Set(); model.gates.forEach(g => { diff --git a/extension/src-language-server/handler.ts b/extension/src-language-server/handler.ts index 1402894..41441d2 100644 --- a/extension/src-language-server/handler.ts +++ b/extension/src-language-server/handler.ts @@ -31,6 +31,7 @@ export function addNotificationHandler(connection: Connection, shared: LangiumSp connection.onNotification('diagram/selected', (msg: {label: string, uri: string}) => { // get the current model const model = getModel(msg.uri, shared); + // const modelFta = getModel(msg.uri, shared); // determine the range in the editor of the component identified by "label" const range = getRangeOfNode(model, msg.label); diff --git a/extension/src-language-server/module.ts b/extension/src-language-server/module.ts index 11d52d8..970516d 100644 --- a/extension/src-language-server/module.ts +++ b/extension/src-language-server/module.ts @@ -58,12 +58,3 @@ export function createServices(context: DefaultSharedModuleContext): { shared: L shared.ServiceRegistry.register(fta); return { shared,stpa, fta}; } - - -/** - * Syntax highlighting from Fta does not work -> - * I tried adding stpa to my example project and there the later added dsl ,stpa, is not highlighted - * Here fta is not highlighted - * Has nothing to do with Sprotty, etc. - * The highlighting probably gets automatically generated for the first language but not for a language thats get added later - */ \ No newline at end of file diff --git a/extension/src-language-server/utils.ts b/extension/src-language-server/utils.ts index 06450fc..5bcf469 100644 --- a/extension/src-language-server/utils.ts +++ b/extension/src-language-server/utils.ts @@ -16,7 +16,7 @@ */ import { LangiumSprottySharedServices } from "langium-sprotty"; -import { Model } from "./generated/ast"; +import { Model, ModelFTA } from "./generated/ast"; import { URI } from 'vscode-uri'; import { LangiumDocument } from "langium"; @@ -30,4 +30,12 @@ export function getModel(uri: string, shared: LangiumSprottySharedServices): Mod const textDocuments = shared.workspace.LangiumDocuments; const currentDoc = textDocuments.getOrCreateDocument(URI.parse(uri)) as LangiumDocument; return currentDoc.parseResult.value; -} \ No newline at end of file +} + +/* +export function getModelFta(uri: string, shared: LangiumSprottySharedServices): ModelFTA { + const textDocuments = shared.workspace.LangiumDocuments; + const currentDoc = textDocuments.getOrCreateDocument(URI.parse(uri)) as LangiumDocument; + return currentDoc.parseResult.value; +} +*/ \ No newline at end of file diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index 4de04d2..223ecce 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -200,6 +200,9 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { if (commandArgs.length === 0 || commandArgs[0] instanceof vscode.Uri && commandArgs[0].path.endsWith('.stpa')) { return 'stpa-diagram'; + }else if(commandArgs.length === 0 + || commandArgs[0] instanceof vscode.Uri && commandArgs[0].path.endsWith('.fta')){ + return 'fta-diagram' } return undefined; } diff --git a/test.fta b/test.fta index 23f6f01..3212f0a 100644 --- a/test.fta +++ b/test.fta @@ -8,7 +8,7 @@ PS "Power supply" B "System bus" Conditions -U "In Use" +U "In Use" Gates G1:INHIBIT [G2,U] diff --git a/teststpa.stpa b/teststpa.stpa index f996cfc..fd67990 100644 --- a/teststpa.stpa +++ b/teststpa.stpa @@ -1,6 +1,7 @@ Losses L1 "test" L2 "FSDFA" +L3 "asd" Hazards From 157cfa3f5c82e782e3aaae314c6b370c9677391c Mon Sep 17 00:00:00 2001 From: OrhanTekin Date: Wed, 17 May 2023 11:08:58 +0200 Subject: [PATCH 06/61] Added diagram options --- extension/package.json | 57 +++++++++++++++++++ .../src-language-server/fta/fta-utils.ts | 18 +++--- 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/extension/package.json b/extension/package.json index 878041f..2de28bd 100644 --- a/extension/package.json +++ b/extension/package.json @@ -131,6 +131,31 @@ "command": "stpa.diagram.export", "title": "Export diagram to SVG", "category": "STPA Diagram" + },{ + "command": "fta.diagram.open", + "title": "Open in Diagram", + "icon": "$(symbol-structure)", + "category": "FTA Diagram" + }, + { + "command": "fta.diagram.fit", + "title": "Fit to Screen", + "category": "FTA Diagram" + }, + { + "command": "fta.diagram.center", + "title": "Center selection", + "category": "FTA Diagram" + }, + { + "command": "fta.diagram.delete", + "title": "Delete selected element", + "category": "FTA Diagram" + }, + { + "command": "fta.diagram.export", + "title": "Export diagram to SVG", + "category": "FTA Diagram" }, { "command": "stpa.checks.setCheckResponsibilitiesForConstraints", @@ -192,6 +217,25 @@ { "command": "stpa.diagram.export", "when": "stpa-diagram-focused" + },{ + "command": "fta.diagram.open", + "when": "editorLangId == 'fta'" + }, + { + "command": "fta.diagram.fit", + "when": "fta-diagram-focused" + }, + { + "command": "fta.diagram.center", + "when": "fta-diagram-focused" + }, + { + "command": "fta.diagram.delete", + "when": "fta-diagram-focused" + }, + { + "command": "fta.diagram.export", + "when": "fta-diagram-focused" }, { "command": "stpa.checks.setCheckResponsibilitiesForConstraints", @@ -224,6 +268,11 @@ { "submenu": "stpa.checks", "group": "checks" + }, + { + "command": "fta.diagram.open", + "when": "editorLangId == 'fta'", + "group": "navigation" } ], "stpa.checks": [ @@ -258,6 +307,10 @@ "command": "stpa.contextTable.open", "when": "editorLangId == 'stpa'", "group": "navigation" + },{ + "command": "fta.diagram.open", + "when": "editorLangId == 'fta'", + "group": "navigation" } ], "explorer/context": [ @@ -270,6 +323,10 @@ "command": "stpa.contextTable.open", "when": "resourceExtname == '.stpa'", "group": "navigation" + },{ + "command": "fta.diagram.open", + "when": "resourceExtname == '.fta'", + "group": "navigation" } ] }, diff --git a/extension/src-language-server/fta/fta-utils.ts b/extension/src-language-server/fta/fta-utils.ts index 2192afb..2cb8755 100644 --- a/extension/src-language-server/fta/fta-utils.ts +++ b/extension/src-language-server/fta/fta-utils.ts @@ -19,7 +19,7 @@ export function getTargets(node: AstNode): AstNode[] { for (const ref of node.child) { if (ref?.ref) { targets.push(ref.ref); } } - }else if(isGate(node)){ + }else{ if(isAND(node.$type) || isOR(node.$type) || isKNGate(node.$type) || isInhibitGate(node.$type) ){ for(const ref of node.$type.child){ if(ref?.ref){targets.push(ref.ref);} @@ -73,9 +73,9 @@ function determineLayerForFTANode(node: FTANode): number { } export function getAndGates(everyGate: Gate[]): AstNode[]{ - let result = []; + let result: AstNode[] = []; for(const gate of everyGate){ - if(isAND(gate)){ + if(isAND(gate)){ // isAnd of an And Gate not working result.push(gate); } } @@ -83,9 +83,9 @@ export function getAndGates(everyGate: Gate[]): AstNode[]{ return result; } export function getOrGates(everyGate: Gate[]): AstNode[]{ - let result = []; + let result: AstNode[] = []; for(const gate of everyGate){ - if(isOR(gate)){ + if(isOR(gate.$type)){ result.push(gate); } } @@ -93,9 +93,9 @@ export function getOrGates(everyGate: Gate[]): AstNode[]{ return result; } export function getkNGates(everyGate: Gate[]): AstNode[]{ - let result = []; + let result: AstNode[] = []; for(const gate of everyGate){ - if(isKNGate(gate)){ + if(isKNGate(gate.$type)){ result.push(gate); } } @@ -103,9 +103,9 @@ export function getkNGates(everyGate: Gate[]): AstNode[]{ return result; } export function getInhibitGates(everyGate: Gate[]): AstNode[]{ - let result = []; + let result: AstNode[] = []; for(const gate of everyGate){ - if(isInhibitGate(gate)){ + if(isInhibitGate(gate.$type)){ result.push(gate); } } From ba262708f80ef709ed188452a91c11fd7fb90b08 Mon Sep 17 00:00:00 2001 From: OrhanTekin Date: Wed, 17 May 2023 12:27:36 +0200 Subject: [PATCH 07/61] Feedback --- test.fta | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/test.fta b/test.fta index 3212f0a..9a00a78 100644 --- a/test.fta +++ b/test.fta @@ -8,18 +8,21 @@ PS "Power supply" B "System bus" Conditions -U "In Use" +U "In Use" + +TopEvent +"System Failure": G1 // "" = G1 //Top event über gates Gates -G1:INHIBIT [G2,U] -//G1:G2 INHIBIT U -G2:OR [G3,B] +//G1:INHIBIT [G2,U] +G1:G2 INHIBIT U // nur infix, G1 = U inhibits G2 +G2:OR [G3,B] //G2: G3 OR B G3:AND [G4,G5] //G3: G4 AND G5 //Alternatively -G4:OR [C1,PS,G6] +G4:OR [C1,PS,G6] // G4 = C1 or PS or G6 G5:OR [C2,PS,G6] -G6:k/N 2 [M1,M2,M3] +G6:k/N 2 [M1,M2,M3] // G6 = 2 of M1, M2, M3 -TopEvent -"System Failure": G1 +//bis zur nächsten präsentation erklären wie man stpa einbindet +//qualiative analyse tauschen mit stpa einbindung \ No newline at end of file From 921515388e56b94e00f5ff9f435fc626824d9e03 Mon Sep 17 00:00:00 2001 From: Orhan Date: Wed, 17 May 2023 16:21:19 +0200 Subject: [PATCH 08/61] Feedback correction --- extension/src-language-server/fta.langium | 19 ++++++++----------- test.fta | 19 +++++++------------ 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/extension/src-language-server/fta.langium b/extension/src-language-server/fta.langium index 7aee623..ea2f1ca 100644 --- a/extension/src-language-server/fta.langium +++ b/extension/src-language-server/fta.langium @@ -3,8 +3,8 @@ grammar Fta entry ModelFTA: ('Components' components+=Component*)? ('Conditions' conditions+=Condition)? - ('Gates' gates+=Gate*)? - ('TopEvent' topEvent=TopEvent); + ('TopEvent' topEvent=TopEvent) + ('Gates' gates+=Gate*)?; Component: name=ID description=STRING; @@ -13,28 +13,25 @@ Condition: name=ID description=STRING; Gate: - name=ID ":" type=(AND|OR|KNGate|InhibitGate); + name=ID "=" type=(AND|OR|KNGate|InhibitGate); TopEvent: - name=STRING ":" child+=[Child:ID]; + name=STRING "=" child+=[Child:ID]; Child: Gate | Component; AND: - (string='AND' '[' child+=[Child:ID] (',' child+=[Child:ID])+ ']' | - child+=[Child:ID] string='AND' child+=[Child:ID]); + child+=[Child:ID] (string='and' child+=[Child:ID])*; OR: - (string='OR' '[' child+=[Child:ID] (',' child+=[Child:ID])+ ']' | - child+=[Child:ID] string='OR' child+=[Child:ID]); + child+=[Child:ID] (string='or' child+=[Child:ID])*; KNGate: - string='k/N' k=INT '[' child+=[Child:ID] (',' child+=[Child:ID])+ ']'; + k=INT string='of' (child+=[Child:ID])*; InhibitGate: - (string='INHIBIT' '[' child+=[Child:ID] ',' condition+=[Condition:ID] ']'| - child+=[Child:ID] string='INHIBIT' condition+=[Condition:ID]); + condition+=[Condition:ID] string='inhibits' child+=[Child:ID]; hidden terminal WS: /\s+/; terminal ID: /[_a-zA-Z][\w_]*/; diff --git a/test.fta b/test.fta index 9a00a78..2357ecc 100644 --- a/test.fta +++ b/test.fta @@ -11,18 +11,13 @@ Conditions U "In Use" TopEvent -"System Failure": G1 // "" = G1 //Top event über gates +"System Failure" = G1 Gates -//G1:INHIBIT [G2,U] -G1:G2 INHIBIT U // nur infix, G1 = U inhibits G2 -G2:OR [G3,B] -//G2: G3 OR B -G3:AND [G4,G5] -//G3: G4 AND G5 //Alternatively -G4:OR [C1,PS,G6] // G4 = C1 or PS or G6 -G5:OR [C2,PS,G6] -G6:k/N 2 [M1,M2,M3] // G6 = 2 of M1, M2, M3 +G1 = U inhibits G2 +G2 = G3 or B +G3 = G4 and G5 +G4 = C1 or PS or G6 +G5 = C2 or PS or G6 +G6 = 2 of M1 M2 M3 -//bis zur nächsten präsentation erklären wie man stpa einbindet -//qualiative analyse tauschen mit stpa einbindung \ No newline at end of file From bcd7d64fbec0729689a613d915a4a99e33ed9bfb Mon Sep 17 00:00:00 2001 From: Orhan Date: Thu, 18 May 2023 11:49:07 +0200 Subject: [PATCH 09/61] Diagram Generation --- extension/src-language-server/fta.langium | 2 +- .../fta/fta-diagram-generator.ts | 45 +++++----- .../src-language-server/fta/fta-utils.ts | 88 ++++++------------- test.fta | 2 +- 4 files changed, 51 insertions(+), 86 deletions(-) diff --git a/extension/src-language-server/fta.langium b/extension/src-language-server/fta.langium index ea2f1ca..5b750a1 100644 --- a/extension/src-language-server/fta.langium +++ b/extension/src-language-server/fta.langium @@ -2,7 +2,7 @@ grammar Fta entry ModelFTA: ('Components' components+=Component*)? - ('Conditions' conditions+=Condition)? + ('Conditions' conditions+=Condition*)? ('TopEvent' topEvent=TopEvent) ('Gates' gates+=Gate*)?; diff --git a/extension/src-language-server/fta/fta-diagram-generator.ts b/extension/src-language-server/fta/fta-diagram-generator.ts index 6cafcb9..ed2d003 100644 --- a/extension/src-language-server/fta/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/fta-diagram-generator.ts @@ -1,12 +1,11 @@ import { AstNode } from 'langium'; import { GeneratorContext, LangiumDiagramGenerator } from 'langium-sprotty'; -import { SModelRoot, SLabel, SModelElement } from 'sprotty-protocol'; -import { ModelFTA, isAND, isCommand, isComponent, isCondition, isGate, isInhibitGate, isKNGate, isOR, isTopEvent, AND } from '../generated/ast'; -import { FtaServices } from './fta-module'; -import { filterModel } from '../stpa/filtering'; +import { SLabel, SModelElement, SModelRoot } from 'sprotty-protocol'; +import { ModelFTA, isComponent, isCondition, isGate, isTopEvent } from '../generated/ast'; import { FTAEdge, FTANode } from './fta-interfaces'; -import { getAndGates, getAspect, getInhibitGates, getOrGates, getTargets, getkNGates, setLevelsForFTANodes } from './fta-utils'; import { FTA_EDGE_TYPE, FTA_NODE_TYPE, PARENT_TYPE } from './fta-model'; +import { FtaServices } from './fta-module'; +import { getAllGateTypes, getAspect, getTargets, setLevelsForFTANodes } from './fta-utils'; export class FtaDiagramGenerator extends LangiumDiagramGenerator{ @@ -26,20 +25,27 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ //set filter for later maybe const filteredModelFTA = model; - // let ftaChildren: SModelElement[] = this.generateAspectWithEdges(filteredModelFTA.topEvent, args); - // Components first -> Gates -> Conditions -> Top Event + let ftaChildren: SModelElement[] = filteredModelFTA.components?.map(l => this.generateFTANode(l, args)); - const andGates = getAndGates(filteredModelFTA.gates); //all gates not listed in debug - const orGates = getOrGates(filteredModelFTA.gates); //aswell as edges missing - const kNGates = getkNGates(filteredModelFTA.gates); - const inhibitGates = getInhibitGates(filteredModelFTA.gates); + + //returns an array of arrays with with every and,or,kn,inhibit-gate at position 1 to 4. + const allGates = getAllGateTypes(filteredModelFTA.gates); + ftaChildren = ftaChildren.concat([ - ...filteredModelFTA.conditions?.map(c => this.generateAspectWithEdges(c,args)).flat(1), - //...filteredModelFTA.gates?.map(g => this.generateAspectWithEdges(g, args)).flat(1), - ...andGates?.map(a => this.generateAspectWithEdges(a, args)).flat(1), - ...orGates?.map(o => this.generateAspectWithEdges(o, args)).flat(1), - ...kNGates?.map(k => this.generateAspectWithEdges(k, args)).flat(1), - ...inhibitGates?.map(i => this.generateAspectWithEdges(i, args)).flat(1), + //first create the ftaNode for the topevent, conditions and all gates + this.generateFTANode(filteredModelFTA.topEvent, args), + ...filteredModelFTA.conditions?.map(c => this.generateFTANode(c,args)).flat(1), + + ...allGates[0]?.map(a => this.generateFTANode(a, args)).flat(1), //and + ...allGates[1]?.map(o => this.generateFTANode(o, args)).flat(1), //or + ...allGates[2]?.map(k => this.generateFTANode(k, args)).flat(1), //kn + ...allGates[3]?.map(i => this.generateFTANode(i, args)).flat(1), //inhib + + //after that create the edges of the gates and the top event + ...allGates[0]?.map(a => this.generateAspectWithEdges(a, args)).flat(1), + ...allGates[1]?.map(o => this.generateAspectWithEdges(o, args)).flat(1), + ...allGates[2]?.map(k => this.generateAspectWithEdges(k, args)).flat(1), + ...allGates[3]?.map(i => this.generateAspectWithEdges(i, args)).flat(1), ...this.generateAspectWithEdges(filteredModelFTA.topEvent, args), ]); @@ -77,10 +83,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ * @returns A node representing {@code node} and edges representing the references {@code node} contains. */ private generateAspectWithEdges(node: AstNode, args: GeneratorContext): SModelElement[] { - // node must be created first in order to access the id when creating the edges - const ftaNode = this.generateFTANode(node, args); const elements: SModelElement[] = this.generateEdgesForFTANode(node, args); - elements.push(ftaNode); return elements; } @@ -97,7 +100,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ // for every reference an edge is created const targets = getTargets(node); for (const target of targets) { - const targetId = idCache.getId(target); + const targetId = idCache.getId(target); // g3: - undefined const edgeId = idCache.uniqueId(`${sourceId}:-:${targetId}`, undefined); if (sourceId && targetId) { const e = this.generateFTAEdge(edgeId, sourceId, targetId, '', args); diff --git a/extension/src-language-server/fta/fta-utils.ts b/extension/src-language-server/fta/fta-utils.ts index 2cb8755..a7d0b8d 100644 --- a/extension/src-language-server/fta/fta-utils.ts +++ b/extension/src-language-server/fta/fta-utils.ts @@ -1,5 +1,5 @@ import { AstNode } from 'langium'; -import { AND, InhibitGate, KNGate, OR, TopEvent, isAND, isComponent, isGate, isInhibitGate, isKNGate, isOR, isTopEvent, Child, isCondition, Gate } from '../generated/ast'; +import { AND, InhibitGate, KNGate, OR, TopEvent, isAND, isComponent, isGate, isInhibitGate, isKNGate, isOR, isTopEvent, Child, isCondition, Gate, Condition } from '../generated/ast'; import { FTANode } from './fta-interfaces'; import { FTAAspect } from './fta-model'; @@ -15,18 +15,16 @@ import { FTAAspect } from './fta-model'; export function getTargets(node: AstNode): AstNode[] { if (node) { const targets: AstNode[] = []; - if(isTopEvent(node)){ //probably wrong + if(isTopEvent(node)){ for (const ref of node.child) { if (ref?.ref) { targets.push(ref.ref); } } - }else{ - if(isAND(node.$type) || isOR(node.$type) || isKNGate(node.$type) || isInhibitGate(node.$type) ){ - for(const ref of node.$type.child){ - if(ref?.ref){targets.push(ref.ref);} - } + }else if(isGate(node)){ + for(const ref of node.type.child){ + if(ref?.ref){targets.push(ref.ref);} //G3 = G4 AND G5 Referencen von G3: ref?.ref gelten wahrscheinlich als undefined } - if(isInhibitGate(node.$type)){ - for(const ref of node.$type.condition){ + if(node.type.$type == 'InhibitGate'){ + for(const ref of node.type.condition){ if(ref?.ref){targets.push(ref.ref);} } } @@ -72,67 +70,31 @@ function determineLayerForFTANode(node: FTANode): number { } } -export function getAndGates(everyGate: Gate[]): AstNode[]{ - let result: AstNode[] = []; - for(const gate of everyGate){ - if(isAND(gate)){ // isAnd of an And Gate not working - result.push(gate); - } - } - - return result; -} -export function getOrGates(everyGate: Gate[]): AstNode[]{ - let result: AstNode[] = []; - for(const gate of everyGate){ - if(isOR(gate.$type)){ - result.push(gate); - } - } - - return result; -} -export function getkNGates(everyGate: Gate[]): AstNode[]{ - let result: AstNode[] = []; - for(const gate of everyGate){ - if(isKNGate(gate.$type)){ - result.push(gate); - } - } +/** Sorts every gate with its type and puts them into a two dimensional array + * @param everyGate Every gate within the FTAModel + * @returns A two dimensional array with every gate sorted into the respective category of And, Or, KN, Inhibit-Gate + */ +export function getAllGateTypes(everyGate: Gate[]): AstNode[][]{ + let andGates: AstNode[] = []; + let orGates: AstNode[] = []; + let kNGates: AstNode[] = []; + let inhibGates: AstNode[] = []; - return result; -} -export function getInhibitGates(everyGate: Gate[]): AstNode[]{ - let result: AstNode[] = []; for(const gate of everyGate){ - if(isInhibitGate(gate.$type)){ - result.push(gate); + if(gate.type.$type == 'AND'){ + andGates.push(gate); + }else if(gate.type.$type == 'OR'){ + orGates.push(gate); + }else if(gate.type.$type == 'KNGate'){ + kNGates.push(gate); + }else if(gate.type.$type == 'InhibitGate'){ + inhibGates.push(gate); } } - + let result: AstNode[][] = [andGates, orGates, kNGates, inhibGates]; return result; } -/* -function setLevels(current: FTANode[], lvl: number, allNodes: FTANode[]): void{ - var next:FTANode[] = []; - for(const currentNode of current){ - currentNode.level = lvl; - if(isAND(currentNode) || isOR(currentNode) || isKNGate(currentNode) || isInhibitGate(currentNode) || isTopEvent(currentNode)){ - for(const child of currentNode.child){ - if(isAND(child) || isOR(child) || isKNGate(child) || isInhibitGate(child) || isComponent(child)){ - if(allNodes.includes(child)){ //Finde - next.push(child); - } - } - } - } - } - - setLevels(next, lvl++, allNodes); -} -*/ - /** * Sets the level property for {@code nodes} depending on the layer they should be in. diff --git a/test.fta b/test.fta index 2357ecc..b59a0c2 100644 --- a/test.fta +++ b/test.fta @@ -15,7 +15,7 @@ TopEvent Gates G1 = U inhibits G2 -G2 = G3 or B +G2 = G3 or B G3 = G4 and G5 G4 = C1 or PS or G6 G5 = C2 or PS or G6 From a838d9d2b4b705d5e44d2a442a4202de6f6602ee Mon Sep 17 00:00:00 2001 From: Orhan Date: Tue, 23 May 2023 13:28:17 +0200 Subject: [PATCH 10/61] Visualisation v1 --- .../fta/fta-diagram-generator.ts | 15 +++- .../src-language-server/fta/fta-interfaces.ts | 2 + .../fta/fta-layout-config.ts | 38 ++++++++++ .../src-language-server/fta/fta-model.ts | 7 +- .../src-language-server/fta/fta-module.ts | 29 ++++++-- .../fta/fta-synthesis-options.ts | 3 +- .../src-language-server/fta/fta-utils.ts | 18 +++-- extension/src-webview/di.config.ts | 8 +- extension/src-webview/fta-model.ts | 59 +++++++++++++++ extension/src-webview/fta-views.tsx | 65 ++++++++++++++++ extension/src-webview/main.ts | 5 +- extension/src-webview/model-viewer.ts | 9 +++ extension/src-webview/views-rendering.tsx | 74 ++++++++++++++++++- extension/src/language-extension.ts | 14 +++- teststpa.stpa | 3 +- 15 files changed, 323 insertions(+), 26 deletions(-) create mode 100644 extension/src-language-server/fta/fta-layout-config.ts create mode 100644 extension/src-webview/fta-model.ts create mode 100644 extension/src-webview/fta-views.tsx diff --git a/extension/src-language-server/fta/fta-diagram-generator.ts b/extension/src-language-server/fta/fta-diagram-generator.ts index ed2d003..0f053c5 100644 --- a/extension/src-language-server/fta/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/fta-diagram-generator.ts @@ -1,7 +1,7 @@ import { AstNode } from 'langium'; import { GeneratorContext, LangiumDiagramGenerator } from 'langium-sprotty'; import { SLabel, SModelElement, SModelRoot } from 'sprotty-protocol'; -import { ModelFTA, isComponent, isCondition, isGate, isTopEvent } from '../generated/ast'; +import { ModelFTA, isComponent, isCondition, isGate, isKNGate, isTopEvent } from '../generated/ast'; import { FTAEdge, FTANode } from './fta-interfaces'; import { FTA_EDGE_TYPE, FTA_NODE_TYPE, PARENT_TYPE } from './fta-model'; import { FtaServices } from './fta-module'; @@ -158,13 +158,22 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ text: node.name } ]; + + let desc = ""; if(isComponent(node) || isCondition(node)){ + desc = node.description; + } + + + if(isGate(node) && isKNGate(node.type)){ return { type: FTA_NODE_TYPE, id: nodeId, aspect: getAspect(node), - description: node.description, + description: desc, children: children, + k: node.type.k, + n: node.type.child.length, layout: 'stack', layoutOptions: { paddingTop: 10.0, @@ -178,7 +187,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ type: FTA_NODE_TYPE, id: nodeId, aspect: getAspect(node), - description: "", + description: desc, children: children, layout: 'stack', layoutOptions: { diff --git a/extension/src-language-server/fta/fta-interfaces.ts b/extension/src-language-server/fta/fta-interfaces.ts index 769ef87..2055bbe 100644 --- a/extension/src-language-server/fta/fta-interfaces.ts +++ b/extension/src-language-server/fta/fta-interfaces.ts @@ -11,6 +11,8 @@ export interface FTANode extends SNode{ description: string highlight?: boolean level?: number + k?: number + n?: number } diff --git a/extension/src-language-server/fta/fta-layout-config.ts b/extension/src-language-server/fta/fta-layout-config.ts new file mode 100644 index 0000000..b78ad9a --- /dev/null +++ b/extension/src-language-server/fta/fta-layout-config.ts @@ -0,0 +1,38 @@ +import { LayoutOptions } from 'elkjs'; +import { DefaultLayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; +import { SEdge, SGraph, SModelElement, SModelIndex, SNode } from 'sprotty-protocol'; +import { FTANode } from './fta-interfaces'; + +export class FtaLayoutConfigurator extends DefaultLayoutConfigurator { + + /* + protected graphOptions(sgraph: SGraph, index: SModelIndex): LayoutOptions { + //super.graphOptions(sgraph, index); + return { + 'org.eclipse.elk.direction': 'DOWN', + 'org.eclipse.elk.spacing.nodeNode': '30.0', + 'org.eclipse.elk.layered.spacing.edgeNodeBetweenLayers': '30.0' + }; + } + + protected nodeOptions(snode: SNode, index: SModelIndex): LayoutOptions | undefined { + return { + 'org.eclipse.elk.nodeLabels.placement': "INSIDE V_CENTER H_CENTER", + 'org.eclipse.elk.partitioning.partition': "" + (snode as FTANode).level, + // nodes with many edges are streched + 'org.eclipse.elk.nodeSize.constraints': 'NODE_LABELS' + } + } + + protected edgeOptions(sedge: SEdge, index: SModelIndex): LayoutOptions | undefined { + return{ + 'org.eclipse.elk.direction': 'DOWN', + } + } + + + apply(element: SModelElement, index: SModelIndex): LayoutOptions | undefined { + return super.apply(element, index); + } + */ +} \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-model.ts b/extension/src-language-server/fta/fta-model.ts index 220f3e6..08f12e6 100644 --- a/extension/src-language-server/fta/fta-model.ts +++ b/extension/src-language-server/fta/fta-model.ts @@ -10,9 +10,12 @@ export const FTA_EDGE_TYPE = 'edge:fta'; * The different aspects of FTA. */ export enum FTAAspect { + TOPEVENT, COMPONENT, CONDITION, - GATE, - TOPEVENT, + AND, + OR, + KN, + INHIBIT, UNDEFINED } \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index 31c3a16..35cbc80 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -10,6 +10,10 @@ import { StpaSynthesisOptions } from '../stpa/synthesis-options'; import { FtaDiagramGenerator } from './fta-diagram-generator'; import { FtaSynthesisOptions } from './fta-synthesis-options'; import { FtaDiagramServer } from './fta-diagramServer'; +import { DefaultElementFilter, DefaultLayoutConfigurator, ElkFactory, ElkLayoutEngine, IElementFilter, ILayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; +import ElkConstructor from 'elkjs/lib/elk.bundled'; +import { StpaLayoutConfigurator } from '../stpa/layout-config'; +import { FtaLayoutConfigurator } from './fta-layout-config'; @@ -24,9 +28,15 @@ export type FtaAddedServices = { validation: { FtaValidator: FtaValidator; } - //options: { - // FtaSynthesisOptions: FtaSynthesisOptions - // } + layout: { + ElkFactory: ElkFactory, + ElementFilter: IElementFilter, + LayoutConfigurator: ILayoutConfigurator; + }, + options: { + FtaSynthesisOptions: FtaSynthesisOptions + } + } /** @@ -43,14 +53,20 @@ export type FtaServices = LangiumSprottyServices & FtaAddedServices export const FtaModule: Module = { diagram: { DiagramGenerator: services => new FtaDiagramGenerator(services), + ModelLayoutEngine: services => new ElkLayoutEngine(services.layout.ElkFactory, services.layout.ElementFilter, services.layout.LayoutConfigurator) as any }, validation: { ValidationRegistry: services => new FtaValidationRegistry(services), FtaValidator: () => new FtaValidator() }, - // options: { - // FtaSynthesisOptions: () => new FtaSynthesisOptions() - // }, + layout: { + ElkFactory: () => () => new ElkConstructor({ algorithms: ['tree'] }), + ElementFilter: () => new DefaultElementFilter, + LayoutConfigurator: () => new FtaLayoutConfigurator //Klappt nicht in dem Moment wo ich FtaLayoutConfig auswähle + }, + options: { + FtaSynthesisOptions: () => new FtaSynthesisOptions() + }, }; export const ftaDiagramServerFactory = @@ -110,7 +126,6 @@ export function createFtaServices(context: DefaultSharedModuleContext): { FtaModule ); shared.ServiceRegistry.register(fta); - // FtaValidationRegistry; return { shared, fta }; } diff --git a/extension/src-language-server/fta/fta-synthesis-options.ts b/extension/src-language-server/fta/fta-synthesis-options.ts index 58176eb..5c12693 100644 --- a/extension/src-language-server/fta/fta-synthesis-options.ts +++ b/extension/src-language-server/fta/fta-synthesis-options.ts @@ -5,10 +5,11 @@ export class FtaSynthesisOptions { private options: ValuedSynthesisOption[]; constructor() { + this.options = []; } getSynthesisOptions(): ValuedSynthesisOption[] { - return []; + return this.options; } } \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-utils.ts b/extension/src-language-server/fta/fta-utils.ts index a7d0b8d..ce7371d 100644 --- a/extension/src-language-server/fta/fta-utils.ts +++ b/extension/src-language-server/fta/fta-utils.ts @@ -42,12 +42,20 @@ export function getTargets(node: AstNode): AstNode[] { * @returns the aspect of {@code node}. */ export function getAspect(node: AstNode): FTAAspect { - if (isComponent(node)) { + if (isTopEvent(node)) { + return FTAAspect.TOPEVENT; + }else if (isComponent(node)) { return FTAAspect.COMPONENT; - } else if (isGate(node)) { - return FTAAspect.GATE; - } else if (isTopEvent(node)) { - return FTAAspect.TOPEVENT; + }else if (isCondition(node)) { + return FTAAspect.CONDITION; + }else if(isGate(node) && isAND(node.type)){ + return FTAAspect.AND; + }else if (isGate(node) && isOR(node.type)) { + return FTAAspect.OR; + }else if (isGate(node) && isKNGate(node.type)) { + return FTAAspect.KN; + }else if (isGate(node) && isInhibitGate(node.type)) { + return FTAAspect.INHIBIT; } return FTAAspect.UNDEFINED; } diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts index bf6ca49..74dff7b 100644 --- a/extension/src-webview/di.config.ts +++ b/extension/src-webview/di.config.ts @@ -29,8 +29,10 @@ import { PolylineArrowEdgeView, STPANodeView, CSNodeView, STPAGraphView } from ' import { STPA_EDGE_TYPE, STPA_NODE_TYPE, STPANode, PARENT_TYPE, CSEdge, CS_EDGE_TYPE, CSNode, CS_NODE_TYPE } from './stpa-model'; import { sidebarModule } from './sidebar'; import { optionsModule } from './options/options-module'; -import { StpaModelViewer } from './model-viewer'; +import { StpaModelViewer, FtaModelViewer } from './model-viewer'; import { StpaMouseListener } from './stpa-mouselistener'; +import { FTANodeView } from './fta-views'; +import { FTANode, FTA_EDGE_TYPE, FTA_NODE_TYPE } from './fta-model'; const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); @@ -55,6 +57,10 @@ const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => configureModelElement(context, CS_EDGE_TYPE, CSEdge, PolylineArrowEdgeView); configureModelElement(context, 'html', HtmlRoot, HtmlRootView); configureModelElement(context, 'pre-rendered', PreRenderedElement, PreRenderedView); + + //FTA + configureModelElement(context, FTA_NODE_TYPE, FTANode, FTANodeView); + configureModelElement(context, FTA_EDGE_TYPE, SEdge, PolylineArrowEdgeView); }); export function createSTPADiagramContainer(widgetId: string): Container { diff --git a/extension/src-webview/fta-model.ts b/extension/src-webview/fta-model.ts new file mode 100644 index 0000000..dbd72f0 --- /dev/null +++ b/extension/src-webview/fta-model.ts @@ -0,0 +1,59 @@ +import { SEdge, SNode, connectableFeature, fadeFeature, hoverFeedbackFeature, layoutContainerFeature, popupFeature, selectFeature } from "sprotty"; + + +// The types of diagram elements +export const FTA_NODE_TYPE = 'node:fta'; +export const PARENT_TYPE = 'node:parent'; +export const EDGE_TYPE = 'edge'; +export const FTA_EDGE_TYPE = 'edge:fta'; + + +/** + * Node representing a FTA component. + */ +export class FTANode extends SNode{ + static readonly DEFAULT_FEATURES = [connectableFeature, selectFeature, + layoutContainerFeature, fadeFeature, hoverFeedbackFeature, popupFeature]; + + aspect: FTAAspect = FTAAspect.UNDEFINED; + description: string = ""; + highlight?: boolean + level?: number; + k?: number + n?: number + +} + + +/** + * Edge representing an edge in the relationship graph. + */ +export class FTAEdge extends SEdge { + highlight?: boolean; +} + + +/** + * The different aspects of FTA. + */ +export enum FTAAspect { + TOPEVENT, + COMPONENT, + CONDITION, + AND, + OR, + KN, + INHIBIT, + UNDEFINED +} + +/** + * Possible edge directions. + */ +export enum EdgeDirection { + UP, + DOWN, + LEFT, + RIGHT, + UNDEFINED +} \ No newline at end of file diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx new file mode 100644 index 0000000..34c5029 --- /dev/null +++ b/extension/src-webview/fta-views.tsx @@ -0,0 +1,65 @@ +/** @jsx svg */ +import { inject, injectable } from 'inversify'; +import { VNode } from "snabbdom"; +import { RectangularNodeView, RenderingContext, SPort, svg } from 'sprotty'; +import { DISymbol } from "./di.symbols"; +import { FTAAspect, FTANode } from './fta-model'; +import { ColorStyleOption, RenderOptionsRegistry } from "./options/render-options-registry"; +import { renderAndGate, renderCircle, renderHexagon, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "./views-rendering"; + + +/** Determines if path/aspect highlighting is currently on. */ +let highlighting: boolean; + + +@injectable() +export class FTANodeView extends RectangularNodeView { + + @inject(DISymbol.RenderOptionsRegistry) renderOptionsRegistry: RenderOptionsRegistry; + + render(node: FTANode, context: RenderingContext): VNode { + + // determines the color of the node + const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption); + const printNode = colorStyle == "black & white"; + const coloredNode = colorStyle == "colorful"; + const sprottyNode = colorStyle == "standard"; + const lessColoredNode = colorStyle == "fewer colors"; + const aspect = node.aspect % 2 == 0 || !lessColoredNode ? node.aspect : node.aspect - 1; + + // create the element based on the option and the aspect of the node + let element: VNode; + switch (node.aspect) { + case FTAAspect.TOPEVENT: + element = renderRectangle(node); + break; + case FTAAspect.COMPONENT: + element = renderCircle(node); + break; + case FTAAspect.CONDITION: + element = renderCircle(node); + break; + case FTAAspect.AND: + element = renderAndGate(node); //not correct yet + break; + case FTAAspect.OR: + element = renderOrGate(node); //not correct yet + break; + case FTAAspect.KN: + element = renderKnGate(node, node.k as number, node.n as number); //not correct yet + break; + case FTAAspect.INHIBIT: + element = renderInhibitGate(node); //wrong + break; + default: + element = renderRectangle(node); + break; + } + return + {element} + {context.renderChildren(node)} + ; + } +} diff --git a/extension/src-webview/main.ts b/extension/src-webview/main.ts index 4050a4d..c2bede8 100644 --- a/extension/src-webview/main.ts +++ b/extension/src-webview/main.ts @@ -18,10 +18,10 @@ import 'reflect-metadata'; import 'sprotty-vscode-webview/css/sprotty-vscode.css'; +import { Container } from 'inversify'; import { SprottyDiagramIdentifier, VscodeDiagramServer } from 'sprotty-vscode-webview'; -import { createSTPADiagramContainer } from './di.config'; import { SprottyLspEditStarter } from 'sprotty-vscode-webview/lib/lsp/editing'; -import { Container } from 'inversify'; +import { createSTPADiagramContainer } from './di.config'; import { StpaDiagramServer } from './diagram-server'; export class StpaSprottyStarter extends SprottyLspEditStarter { @@ -37,3 +37,4 @@ export class StpaSprottyStarter extends SprottyLspEditStarter { } new StpaSprottyStarter(); + diff --git a/extension/src-webview/model-viewer.ts b/extension/src-webview/model-viewer.ts index 93ea356..7c8d066 100644 --- a/extension/src-webview/model-viewer.ts +++ b/extension/src-webview/model-viewer.ts @@ -18,6 +18,7 @@ import { inject, postConstruct } from "inversify"; import { ModelViewer } from "sprotty"; import { DISymbol } from "./di.symbols"; +import { Model } from '../src-language-server/generated/ast'; export class StpaModelViewer extends ModelViewer { // @ts-ignore @@ -28,4 +29,12 @@ export class StpaModelViewer extends ModelViewer { } +} + +export class FtaModelViewer extends ModelViewer{ + + @postConstruct() + init(): void { + + } } \ No newline at end of file diff --git a/extension/src-webview/views-rendering.tsx b/extension/src-webview/views-rendering.tsx index ab4b49c..613539b 100644 --- a/extension/src-webview/views-rendering.tsx +++ b/extension/src-webview/views-rendering.tsx @@ -16,8 +16,9 @@ */ /** @jsx svg */ -import { VNode } from 'snabbdom'; +import { h, VNode } from 'snabbdom'; import { SNode, svg } from 'sprotty'; +import * as path from 'path'; /** * Creates a circle for {@code node}. @@ -170,3 +171,74 @@ export function renderHexagon(node: SNode): VNode { />; } + +export function renderAndGate(node:SNode): VNode{ + const leftX = 0; + const rightX = Math.max(node.size.width, 0); + const midX = Math.max(node.size.width, 0) / 2.0; + const botY = Math.max(node.size.height, 0); + const midY = Math.max(node.size.height, 0) / 2.0; + const topY = 0; + const d = 'M' + leftX + " " + botY + " L " + leftX + " " + midY + " C " + leftX + " " + topY + " " + rightX + " " + topY + " " + rightX + " " + midY + + " L " + rightX + " " + botY + 'Z'; + + return ; +} + + +export function renderOrGate(node:SNode): VNode{ + const leftX = 0; + const rightX = Math.max(node.size.width - 5.0, 0); + const midX = rightX / 2.0; + const botY = Math.max(node.size.height, 0); + const nearBotY = Math.max(node.size.height, 0) - (Math.max(node.size.height, 0) / 10.0); + const midY = Math.max(node.size.height, 0) / 2; + const topY = 0; + const d = 'M' + leftX + " " + botY + " L " + leftX + " " + midY + " C " + midX + " " + topY + " "+ midX + " " + topY + " " + rightX + " " + midY + + " L " + rightX + " " + botY + " L " + midX + " " + nearBotY + " Z "; + + return +} + +export function renderKnGate(node:SNode, k:number, n:number): VNode{ + const leftX = 0; + const rightX = Math.max(node.size.width - 5.0, 0); + const midX = rightX / 2.0; + const botY = Math.max(node.size.height, 0); + const nearBotY = Math.max(node.size.height, 0) - (Math.max(node.size.height, 0) / 10.0); + const midY = Math.max(node.size.height, 0) / 2; + const topY = 0; + const d = 'M' + leftX + " " + botY + " L " + leftX + " " + midY + " C " + midX + " " + topY + " "+ midX + " " + topY + " " + rightX + " " + midY + + " L " + rightX + " " + botY + " L " + midX + " " + nearBotY + " Z "; + + return ( + + + + {`${k}/${n}`} + + + ); +} + + +export function renderInhibitGate(node:SNode): VNode{ + const leftX = 0; + const midX = Math.max(node.size.width, 0) / 2.0 ; + const rightX = Math.max(node.size.width, 0); + const lowestY = Math.max(node.size.height, 0); + const lowY = Math.max(node.size.height, 0) - (Math.max(node.size.height, 0) / 4.0); + const highY = Math.max(node.size.height, 0) / 4.0; + const highestY = 0; + + const d = 'M' + leftX + " " + lowY + " L " + leftX + " " + highY + " L " + midX + " " + highestY + " L " + rightX + " " + highY + + " L " + rightX + " " + lowY + " L " + midX + " " + lowestY; + + return +} \ No newline at end of file diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index 223ecce..6080e46 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -66,6 +66,14 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { editor.revealRange(editor.selection, vscode.TextEditorRevealType.InCenter); } }); + //Active text editor changed + // vscode.window.onDidChangeActiveTextEditor(activeEditor => { + // if(activeEditor){ + // Change extension Prefix based on command arg + //Change diagram type based on command arg + // this.getDiagramType(); + // } + // }); // textdocument has changed vscode.workspace.onDidChangeTextDocument(changeEvent => { this.handleTextChangeEvent(changeEvent); }); @@ -200,11 +208,11 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { if (commandArgs.length === 0 || commandArgs[0] instanceof vscode.Uri && commandArgs[0].path.endsWith('.stpa')) { return 'stpa-diagram'; - }else if(commandArgs.length === 0 - || commandArgs[0] instanceof vscode.Uri && commandArgs[0].path.endsWith('.fta')){ + } + if(commandArgs[0] instanceof vscode.Uri && commandArgs[0].path.endsWith('.fta')){ return 'fta-diagram' } - return undefined; + return undefined; } createContextTable(): void { diff --git a/teststpa.stpa b/teststpa.stpa index fd67990..e8156ce 100644 --- a/teststpa.stpa +++ b/teststpa.stpa @@ -4,4 +4,5 @@ L2 "FSDFA" L3 "asd" Hazards - +H1 "" [L1] +H2 "" [L2] From 737f3b99f2a8825fb88d0f7d473ca087aba9c98f Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Tue, 23 May 2023 14:29:53 +0200 Subject: [PATCH 11/61] fixed fta.diagram.open --- extension/package.json | 41 +++++++---------------------- extension/src/language-extension.ts | 2 +- 2 files changed, 11 insertions(+), 32 deletions(-) diff --git a/extension/package.json b/extension/package.json index 2de28bd..a5eb3b2 100644 --- a/extension/package.json +++ b/extension/package.json @@ -101,10 +101,10 @@ }, "commands": [ { - "command": "stpa.diagram.open", + "command": "pasta.diagram.open", "title": "Open in Diagram", "icon": "$(symbol-structure)", - "category": "STPA Diagram" + "category": "PASTA Diagram" }, { "command": "stpa.contextTable.open", @@ -131,11 +131,6 @@ "command": "stpa.diagram.export", "title": "Export diagram to SVG", "category": "STPA Diagram" - },{ - "command": "fta.diagram.open", - "title": "Open in Diagram", - "icon": "$(symbol-structure)", - "category": "FTA Diagram" }, { "command": "fta.diagram.fit", @@ -191,8 +186,8 @@ "menus": { "commandPalette": [ { - "command": "stpa.diagram.open", - "when": "editorLangId == 'stpa'" + "command": "pasta.diagram.open", + "when": "editorLangId == 'stpa' || editorLangId == 'fta'" }, { "command": "stpa.contextTable.open", @@ -217,9 +212,6 @@ { "command": "stpa.diagram.export", "when": "stpa-diagram-focused" - },{ - "command": "fta.diagram.open", - "when": "editorLangId == 'fta'" }, { "command": "fta.diagram.fit", @@ -256,8 +248,8 @@ ], "editor/context": [ { - "command": "stpa.diagram.open", - "when": "editorLangId == 'stpa'", + "command": "pasta.diagram.open", + "when": "editorLangId == 'stpa' || editorLangId == 'fta'", "group": "navigation" }, { @@ -268,11 +260,6 @@ { "submenu": "stpa.checks", "group": "checks" - }, - { - "command": "fta.diagram.open", - "when": "editorLangId == 'fta'", - "group": "navigation" } ], "stpa.checks": [ @@ -299,34 +286,26 @@ ], "editor/title": [ { - "command": "stpa.diagram.open", - "when": "editorLangId == 'stpa'", + "command": "pasta.diagram.open", + "when": "editorLangId == 'stpa' || editorLangId == 'fta'", "group": "navigation" }, { "command": "stpa.contextTable.open", "when": "editorLangId == 'stpa'", "group": "navigation" - },{ - "command": "fta.diagram.open", - "when": "editorLangId == 'fta'", - "group": "navigation" } ], "explorer/context": [ { - "command": "stpa.diagram.open", - "when": "resourceExtname == '.stpa'", + "command": "pasta.diagram.open", + "when": "resourceExtname == '.stpa' || resourceExtname == '.fta'", "group": "navigation" }, { "command": "stpa.contextTable.open", "when": "resourceExtname == '.stpa'", "group": "navigation" - },{ - "command": "fta.diagram.open", - "when": "resourceExtname == '.fta'", - "group": "navigation" } ] }, diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index 6080e46..dbd2565 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -42,7 +42,7 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { protected ignoreNextTextChange: boolean = false; constructor(context: vscode.ExtensionContext) { - super('stpa', context); + super('pasta', context); // user changed configuration settings vscode.workspace.onDidChangeConfiguration(() => { this.updateViews(this.languageClient, this.lastUri); From 128c5a6fdf50f6eded3bae3b331ec2ca9ee21d76 Mon Sep 17 00:00:00 2001 From: Orhan Date: Fri, 26 May 2023 11:04:32 +0200 Subject: [PATCH 12/61] .css --- extension/src-language-server/fta.langium | 6 +- .../fta/fta-diagram-generator.ts | 8 +- .../fta/fta-layout-config.ts | 40 +-- .../src-language-server/fta/fta-module.ts | 4 +- .../src-language-server/fta/fta-utils.ts | 107 ++++-- extension/src-webview/css/diagram.css | 3 +- extension/src-webview/css/fta-diagram.css | 333 ++++++++++++++++++ extension/src-webview/css/fta-theme.css | 41 +++ extension/src-webview/di.config.ts | 4 +- extension/src-webview/fta-views.tsx | 21 +- extension/src-webview/model-viewer.ts | 8 - extension/src-webview/views-rendering.tsx | 27 +- test.fta | 4 +- teststpa.stpa | 6 + 14 files changed, 532 insertions(+), 80 deletions(-) create mode 100644 extension/src-webview/css/fta-diagram.css create mode 100644 extension/src-webview/css/fta-theme.css diff --git a/extension/src-language-server/fta.langium b/extension/src-language-server/fta.langium index 5b750a1..5d2fce3 100644 --- a/extension/src-language-server/fta.langium +++ b/extension/src-language-server/fta.langium @@ -22,13 +22,13 @@ Child: Gate | Component; AND: - child+=[Child:ID] (string='and' child+=[Child:ID])*; + child+=[Child:ID] (string='and' child+=[Child:ID])+; OR: - child+=[Child:ID] (string='or' child+=[Child:ID])*; + child+=[Child:ID] (string='or' child+=[Child:ID])+; KNGate: - k=INT string='of' (child+=[Child:ID])*; + k=INT string='of' child+=[Child:ID] (',' child+=[Child:ID])+; InhibitGate: condition+=[Condition:ID] string='inhibits' child+=[Child:ID]; diff --git a/extension/src-language-server/fta/fta-diagram-generator.ts b/extension/src-language-server/fta/fta-diagram-generator.ts index 0f053c5..47ef959 100644 --- a/extension/src-language-server/fta/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/fta-diagram-generator.ts @@ -58,9 +58,15 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ ftaNodes.push(node as FTANode); } } + const ftaEdges: FTAEdge[] = []; + for(const edge of ftaChildren){ + if(edge.type === FTA_EDGE_TYPE){ + ftaEdges.push(edge as FTAEdge) + } + } // give the top event the level 0 - setLevelsForFTANodes(ftaNodes); + setLevelsForFTANodes(ftaNodes, ftaEdges); return { type: 'graph', diff --git a/extension/src-language-server/fta/fta-layout-config.ts b/extension/src-language-server/fta/fta-layout-config.ts index b78ad9a..72d8c8e 100644 --- a/extension/src-language-server/fta/fta-layout-config.ts +++ b/extension/src-language-server/fta/fta-layout-config.ts @@ -2,37 +2,37 @@ import { LayoutOptions } from 'elkjs'; import { DefaultLayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; import { SEdge, SGraph, SModelElement, SModelIndex, SNode } from 'sprotty-protocol'; import { FTANode } from './fta-interfaces'; +import { FTAAspect, FTA_NODE_TYPE } from './fta-model'; export class FtaLayoutConfigurator extends DefaultLayoutConfigurator { - /* + protected graphOptions(sgraph: SGraph, index: SModelIndex): LayoutOptions { - //super.graphOptions(sgraph, index); + //options for the entire graph. return { - 'org.eclipse.elk.direction': 'DOWN', + 'org.eclipse.elk.partitioning.activate': 'true', 'org.eclipse.elk.spacing.nodeNode': '30.0', - 'org.eclipse.elk.layered.spacing.edgeNodeBetweenLayers': '30.0' + 'org.eclipse.elk.direction': 'DOWN', + 'org.eclipse.elk.layered.spacing.edgeNodeBetweenLayers': '30.0', + 'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': '30.0', }; } protected nodeOptions(snode: SNode, index: SModelIndex): LayoutOptions | undefined { + //options for the nodes. + const level = (snode as FTANode).level; return { + 'org.eclipse.elk.layered.thoroughness': '70', + 'org.eclipse.elk.partitioning.activate': 'true', 'org.eclipse.elk.nodeLabels.placement': "INSIDE V_CENTER H_CENTER", - 'org.eclipse.elk.partitioning.partition': "" + (snode as FTANode).level, - // nodes with many edges are streched - 'org.eclipse.elk.nodeSize.constraints': 'NODE_LABELS' - } - } + 'org.eclipse.elk.partitioning.partition': "" + level, + 'org.eclipse.elk.layered.nodePlacement.layerConstraint': "" + level, - protected edgeOptions(sedge: SEdge, index: SModelIndex): LayoutOptions | undefined { - return{ - 'org.eclipse.elk.direction': 'DOWN', - } - } - - - apply(element: SModelElement, index: SModelIndex): LayoutOptions | undefined { - return super.apply(element, index); - } - */ + //'org.eclipse.elk.nodeSize.constraints': 'NODE_LABELS', + 'org.eclipse.elk.direction' : 'DOWN', + 'org.eclipse.elk.algorithm': 'layered', + 'org.eclipse.elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX', + 'org.eclipse.elk.spacing.portsSurrounding': '[top=10.0,left=10.0,bottom=10.0,right=10.0]' + } + } } \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index 35cbc80..f7e25c8 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -60,9 +60,9 @@ export const FtaModule: Module new FtaValidator() }, layout: { - ElkFactory: () => () => new ElkConstructor({ algorithms: ['tree'] }), + ElkFactory: () => () => new ElkConstructor({ algorithms: ['layered'] }), ElementFilter: () => new DefaultElementFilter, - LayoutConfigurator: () => new FtaLayoutConfigurator //Klappt nicht in dem Moment wo ich FtaLayoutConfig auswähle + LayoutConfigurator: () => new FtaLayoutConfigurator }, options: { FtaSynthesisOptions: () => new FtaSynthesisOptions() diff --git a/extension/src-language-server/fta/fta-utils.ts b/extension/src-language-server/fta/fta-utils.ts index ce7371d..0858934 100644 --- a/extension/src-language-server/fta/fta-utils.ts +++ b/extension/src-language-server/fta/fta-utils.ts @@ -1,7 +1,8 @@ import { AstNode } from 'langium'; import { AND, InhibitGate, KNGate, OR, TopEvent, isAND, isComponent, isGate, isInhibitGate, isKNGate, isOR, isTopEvent, Child, isCondition, Gate, Condition } from '../generated/ast'; -import { FTANode } from './fta-interfaces'; +import { FTAEdge, FTANode } from './fta-interfaces'; import { FTAAspect } from './fta-model'; +import { FTA_NODE_TYPE } from '../../src-webview/fta-model'; @@ -61,23 +62,6 @@ export function getAspect(node: AstNode): FTAAspect { } - -/** - * Determines the layer {@code node} should be in depending on the FTA aspect it represents. - * @param node FTANode for which the layer should be determined. - * @param hazardDepth Maximal depth of the hazard hierarchy. - * @param sysConsDepth Maximal depth of the system-level constraint hierarchy. - * @returns The number of the layer {@code node} should be in. - */ -function determineLayerForFTANode(node: FTANode): number { - switch (node.aspect) { - case FTAAspect.TOPEVENT: - return 0; - default: - return -1; - } -} - /** Sorts every gate with its type and puts them into a two dimensional array * @param everyGate Every gate within the FTAModel * @returns A two dimensional array with every gate sorted into the respective category of And, Or, KN, Inhibit-Gate @@ -108,12 +92,89 @@ export function getAllGateTypes(everyGate: Gate[]): AstNode[][]{ * Sets the level property for {@code nodes} depending on the layer they should be in. * @param nodes The nodes representing the stpa components. */ -export function setLevelsForFTANodes(nodes: FTANode[]): void{ +export function setLevelsForFTANodes(nodes: FTANode[], edges: FTAEdge[]): void{ + //start with the top event + let topevent: FTANode[] = [getTopEvent(nodes)]; + determineLevelForChildren(topevent, 0, edges, nodes); +} - for(const node of nodes){ - const level = determineLayerForFTANode(node); - node.level = level; - } +/** + * Returns the top event. + * @param nodes All nodes. + * @returns the top event from all nodes. + */ +function getTopEvent(nodes: FTANode[]): FTANode{ + for(const node of nodes){ + if(node.aspect === FTAAspect.TOPEVENT){ + return node; + } + } + let empty = {} as FTANode; + return empty; +} +/** + * Recursively determine the level of all nodes, starting with the top event. + * @param nodes All the nodes on the current layer we want to look at. At the start, this is just the top event. + * @param layer The current layer we want to assign. + * @param edges All edges in the graph. + * @param allNodes All nodes in the graph. + */ +function determineLevelForChildren(nodes: FTANode[], level: number, edges: FTAEdge[], allNodes: FTANode[]): void{ + let children: FTANode[] = []; + + for(const node of nodes){ + //for every node on current layer assign the level. + node.level = level; + for(const edge of edges){ + //Look at every edge that starts from our current node. + if(edge.sourceId === node.id){ + //Get child that is connected to the second part of the edge + const child = getChildWithID(allNodes, edge.targetId); //Edge from G1 to G3 (G1:-:G3) with G3 being the targetId. + if(!children.some((c) => c.id === child.id) && child.aspect !== FTAAspect.CONDITION){ //Don't add conditions yet. + children.push(getChildWithID(allNodes, edge.targetId)); + } + } + } + } + //If there is an inhibit gate in the next iteration/layer, then also + //add Condition to children, so that they can be on the same layer as the inhibit gates. + for(const node of children){ + if(node.aspect == FTAAspect.INHIBIT){ + for(const edge of edges){ + if(edge.sourceId === node.id){ + node.level = level; + const child = getChildWithID(allNodes, edge.targetId); + if(child.aspect === FTAAspect.CONDITION){ + children.push(child); + } + } + } + } + } + + level++; + //Only repeat until there is no layer below + if(children.length != 0){ + determineLevelForChildren(children, level, edges, allNodes); + } + +} + + +/** + * Gets a child object with its id from all nodes. + * @param nodes All FtaNodes we want to assign levels to. + * @param id The id of Node. + * @returns an FTANode with the given id. + */ +function getChildWithID(nodes: FTANode[], id: String): FTANode{ + for(const node of nodes){ + if(node.id == id){ + return node; + } + } + const empty = {} as FTANode; + return empty; } diff --git a/extension/src-webview/css/diagram.css b/extension/src-webview/css/diagram.css index fa6c15b..dc4adee 100644 --- a/extension/src-webview/css/diagram.css +++ b/extension/src-webview/css/diagram.css @@ -1,6 +1,7 @@ @import "./options.css"; @import "./sidebar.css"; @import "./theme.css"; +@import "./fta-diagram.css"; .vscode-high-contrast .stpa-node[aspect="0"], .vscode-high-contrast .stpa-edge-arrow[aspect="0"] { @@ -465,4 +466,4 @@ body[class='vscode-light'] .sprotty-edge-arrow { .node-selected { stroke-width: 5; stroke: var(--vscode-button-hover-background); -} \ No newline at end of file +} diff --git a/extension/src-webview/css/fta-diagram.css b/extension/src-webview/css/fta-diagram.css new file mode 100644 index 0000000..6e806ba --- /dev/null +++ b/extension/src-webview/css/fta-diagram.css @@ -0,0 +1,333 @@ +@import "./fta-theme.css"; + + + +.vscode-high-contrast .fta-node[aspect="0"], +.vscode-high-contrast .fta-edge-arrow[aspect="0"] { + fill: #AD0000; +} + +.vscode-high-contrast .fta-node[aspect="1"], +.vscode-high-contrast .fta-edge-arrow[aspect="1"] { + fill: #A85400; +} + +.vscode-high-contrast .fta-node[aspect="2"], +.vscode-high-contrast .fta-edge-arrow[aspect="2"] { + fill: #006600; +} + +.vscode-high-contrast .fta-node[aspect="3"], +.vscode-high-contrast .fta-edge-arrow[aspect="3"] { + fill: purple; +} + +.vscode-high-contrast .fta-node[aspect="4"], +.vscode-high-contrast .fta-edge-arrow[aspect="4"] { + fill: #0054A8; +} + +.vscode-high-contrast .fta-node[aspect="5"], +.vscode-high-contrast .fta-edge-arrow[aspect="5"] { + fill: #006161; +} + +.vscode-high-contrast .fta-node[aspect="6"], +.vscode-high-contrast .fta-edge-arrow[aspect="6"] { + fill: #616100; +} + + + +.vscode-high-contrast .fta-edge[aspect="0"], +.vscode-high-contrast .fta-edge-arrow[aspect="0"] { + stroke: #AD0000; +} + +.vscode-high-contrast .fta-edge[aspect="1"], +.vscode-high-contrast .fta-edge-arrow[aspect="1"] { + stroke: #A85400; +} + +.vscode-high-contrast .fta-edge[aspect="2"], +.vscode-high-contrast .fta-edge-arrow[aspect="2"] { + stroke: #006600; +} + +.vscode-high-contrast .fta-edge[aspect="3"], +.vscode-high-contrast .fta-edge-arrow[aspect="3"] { + stroke: purple; +} + +.vscode-high-contrast .fta-edge[aspect="4"], +.vscode-high-contrast .fta-edge-arrow[aspect="4"] { + stroke: #0054A8; +} + +.vscode-high-contrast .fta-edge[aspect="5"], +.vscode-high-contrast .fta-edge-arrow[aspect="5"] { + stroke: #006161; +} + +.vscode-high-contrast .fta-edge[aspect="6"], +.vscode-high-contrast .fta-edge-arrow[aspect="6"] { + stroke: #616100; +} + +.vscode-high-contrast .fta-edge[aspect="7"], +.vscode-high-contrast .fta-edge-arrow[aspect="7"] { + stroke: #2f4f4f; +} + + +.vscode-high-contrast .print-node { + fill: black; + stroke: white; +} + +.vscode-high-contrast .sprotty-node { + fill: #0c598d; + stroke: black; +} + + +.vscode-dark .fta-node[aspect="0"] { + fill: var(--fta-dark-node); + stroke: var(--fta-topevent-dark); + stroke-width: 2; +} + +.vscode-dark .fta-node[aspect="1"] { + fill: var(--fta-dark-node); + stroke: var(--fta-component-dark); + stroke-width: 2; +} + +.vscode-dark .fta-node[aspect="2"] { + fill: var(--fta-dark-node); + stroke: var(--fta-condition-dark); + stroke-width: 2; +} + +.vscode-dark .fta-node[aspect="3"] { + fill: var(--fta-dark-node); + stroke: var(--fta-and-dark); + stroke-width: 2; +} + +.vscode-dark .fta-node[aspect="4"] { + fill: var(--fta-dark-node); + stroke: var(--fta-or-dark); + stroke-width: 2; +} + +.vscode-dark .fta-node[aspect="5"] { + fill: var(--fta-dark-node); + stroke: var(--fta-kn-dark); + stroke-width: 2; +} + +.vscode-dark .fta-node[aspect="6"] { + fill: var(--fta-dark-node); + stroke: var(--fta-inhibit-dark); + stroke-width: 2; +} + +.vscode-dark .fta-edge-arrow[aspect="0"] { + fill: var(--fta-topevent-dark); +} + +.vscode-dark .fta-edge-arrow[aspect="1"] { + fill: var(--fta-component-dark); +} + +.vscode-dark .fta-edge-arrow[aspect="2"] { + fill: var(--fta-condition-dark); +} + +.vscode-dark .fta-edge-arrow[aspect="3"] { + fill: var(--fta-and-dark); +} + +.vscode-dark .fta-edge-arrow[aspect="4"] { + fill: var(--fta-or-dark); +} + +.vscode-dark .fta-edge-arrow[aspect="5"] { + fill: var(--fta-kn-dark); +} + +.vscode-dark .fta-edge-arrow[aspect="6"] { + fill: var(--fta-inhibit-dark); +} + + +.vscode-dark .fta-edge[aspect="0"], +.vscode-dark .fta-edge-arrow[aspect="0"] { + stroke: var(--fta-topevent-dark); +} + +.vscode-dark .fta-edge[aspect="1"], +.vscode-dark .fta-edge-arrow[aspect="1"] { + stroke: var(--fta-component-dark); +} + +.vscode-dark .fta-edge[aspect="2"], +.vscode-dark .fta-edge-arrow[aspect="2"] { + stroke: var(--fta-condition-dark); +} + +.vscode-dark .fta-edge[aspect="3"], +.vscode-dark .fta-edge-arrow[aspect="3"] { + stroke: var(--fta-and-dark); +} + +.vscode-dark .fta-edge[aspect="4"], +.vscode-dark .fta-edge-arrow[aspect="4"] { + stroke: var(--fta-or-dark); +} + +.vscode-dark .fta-edge[aspect="5"], +.vscode-dark .fta-edge-arrow[aspect="5"] { + stroke: var(--fta-kn-dark); +} + +.vscode-dark .fta-edge[aspect="6"], +.vscode-dark .fta-edge-arrow[aspect="6"] { + stroke: var(--fta-inhibit-dark); +} + +.vscode-dark .print-node { + fill: var(--fta-dark-node); + stroke: var(--vscode-editor-foreground); +} + +.vscode-dark .sprotty-node { + fill: #0c598d; + stroke: black; +} + + + +.vscode-light .fta-node[aspect="0"] { + fill: var(--fta-light-node); + stroke: var(--fta-topevent-light); + stroke-width: 2; +} + +.vscode-light .fta-node[aspect="1"] { + fill: var(--fta-light-node); + stroke: var(--fta-component-light); + stroke-width: 2; +} + +.vscode-light .fta-node[aspect="2"] { + fill: var(--fta-light-node); + stroke: var(--fta-condition-light); + stroke-width: 2; +} + +.vscode-light .fta-node[aspect="3"] { + fill: var(--fta-light-node); + stroke: var(--fta-and-light); + stroke-width: 2; +} + +.vscode-light .fta-node[aspect="4"] { + fill: var(--fta-light-node); + stroke: var(--fta-or-light); + stroke-width: 2; +} + +.vscode-light .fta-node[aspect="5"] { + fill: var(--fta-light-node); + stroke: var(--fta-kn-light); + stroke-width: 2; +} + +.vscode-light .fta-node[aspect="6"] { + fill: var(--fta-light-node); + stroke: var(--fta-inhibit-light); + stroke-width: 2; +} + +.vscode-light .fta-edge-arrow[aspect="0"] { + fill: var(--fta-topevent-light); +} + +.vscode-light .fta-edge-arrow[aspect="1"] { + fill: var(--fta-component-light); +} + +.vscode-light .fta-edge-arrow[aspect="2"] { + fill: var(--fta-condition-light); +} + +.vscode-light .fta-edge-arrow[aspect="3"] { + fill: var(--fta-and-light); +} + +.vscode-light .fta-edge-arrow[aspect="4"] { + fill: var(--fta-or-light); +} + +.vscode-light .fta-edge-arrow[aspect="5"] { + fill: var(--fta-kn-light); +} + +.vscode-light .fta-edge-arrow[aspect="6"] { + fill: var(--fta-inhibit-light); +} + + +.vscode-light .fta-edge[aspect="0"], +.vscode-light .fta-edge-arrow[aspect="0"] { + stroke: var(--fta-topevent-light); +} + +.vscode-light .fta-edge[aspect="1"], +.vscode-light .fta-edge-arrow[aspect="1"] { + stroke: var(--fta-component-light); +} + +.vscode-light .fta-edge[aspect="2"], +.vscode-light .fta-edge-arrow[aspect="2"] { + stroke: var(--fta-condition-light); +} + +.vscode-light .fta-edge[aspect="3"], +.vscode-light .fta-edge-arrow[aspect="3"] { + stroke: var(--fta-and-light); +} + +.vscode-light .fta-edge[aspect="4"], +.vscode-light .fta-edge-arrow[aspect="4"] { + stroke: var(--fta-or-light); +} + +.vscode-light .fta-edge[aspect="5"], +.vscode-light .fta-edge-arrow[aspect="5"] { + stroke: var(--fta-kn-light); +} + +.vscode-light .fta-edge[aspect="6"], +.vscode-light .fta-edge-arrow[aspect="6"] { + stroke: var(--fta-inhibit-light); +} + +.vscode-light .print-node { + fill: var(--fta-light-node); + stroke: black; +} + +.vscode-light .sprotty-node { + fill: #50b1f1; + stroke: black; +} + + + + +.fta-node { + stroke: black; +} \ No newline at end of file diff --git a/extension/src-webview/css/fta-theme.css b/extension/src-webview/css/fta-theme.css new file mode 100644 index 0000000..1077843 --- /dev/null +++ b/extension/src-webview/css/fta-theme.css @@ -0,0 +1,41 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2022 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + + :root { + + + /* Colors for fta aspects */ + --fta-topevent-dark: #AD0000; + --fta-component-dark: #A85400; + --fta-condition-dark: #006600; + --fta-and-dark: purple; + --fta-or-dark: #0054A8; + --fta-kn-constraint-dark: #006161; + --fta-inhibit-dark: #616100; + + --fta-dark-node: #ffffff00; + --fta-light-node: white; + + --fta-topevent-light: #FF2828; + --fta-component-light: #ffa852; + --fta-condition-light: #00eb00; + --fta-and-light: #ff6bff; + --fta-or-light: #66b3ff; + --fta-kn-light: #00cccc; + --fta-inhibit-light: #cccc00; + +} diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts index 74dff7b..0940b13 100644 --- a/extension/src-webview/di.config.ts +++ b/extension/src-webview/di.config.ts @@ -29,7 +29,7 @@ import { PolylineArrowEdgeView, STPANodeView, CSNodeView, STPAGraphView } from ' import { STPA_EDGE_TYPE, STPA_NODE_TYPE, STPANode, PARENT_TYPE, CSEdge, CS_EDGE_TYPE, CSNode, CS_NODE_TYPE } from './stpa-model'; import { sidebarModule } from './sidebar'; import { optionsModule } from './options/options-module'; -import { StpaModelViewer, FtaModelViewer } from './model-viewer'; +import { StpaModelViewer} from './model-viewer'; import { StpaMouseListener } from './stpa-mouselistener'; import { FTANodeView } from './fta-views'; import { FTANode, FTA_EDGE_TYPE, FTA_NODE_TYPE } from './fta-model'; @@ -57,7 +57,7 @@ const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => configureModelElement(context, CS_EDGE_TYPE, CSEdge, PolylineArrowEdgeView); configureModelElement(context, 'html', HtmlRoot, HtmlRootView); configureModelElement(context, 'pre-rendered', PreRenderedElement, PreRenderedView); - + //FTA configureModelElement(context, FTA_NODE_TYPE, FTANode, FTANodeView); configureModelElement(context, FTA_EDGE_TYPE, SEdge, PolylineArrowEdgeView); diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index 34c5029..caac257 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -4,8 +4,8 @@ import { VNode } from "snabbdom"; import { RectangularNodeView, RenderingContext, SPort, svg } from 'sprotty'; import { DISymbol } from "./di.symbols"; import { FTAAspect, FTANode } from './fta-model'; -import { ColorStyleOption, RenderOptionsRegistry } from "./options/render-options-registry"; -import { renderAndGate, renderCircle, renderHexagon, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "./views-rendering"; +import { RenderOptionsRegistry } from "./options/render-options-registry"; +import { renderAndGate, renderCircle, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "./views-rendering"; /** Determines if path/aspect highlighting is currently on. */ @@ -19,15 +19,8 @@ export class FTANodeView extends RectangularNodeView { render(node: FTANode, context: RenderingContext): VNode { - // determines the color of the node - const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption); - const printNode = colorStyle == "black & white"; - const coloredNode = colorStyle == "colorful"; - const sprottyNode = colorStyle == "standard"; - const lessColoredNode = colorStyle == "fewer colors"; - const aspect = node.aspect % 2 == 0 || !lessColoredNode ? node.aspect : node.aspect - 1; - // create the element based on the option and the aspect of the node + // create the element based on the aspect of the node let element: VNode; switch (node.aspect) { case FTAAspect.TOPEVENT: @@ -40,16 +33,16 @@ export class FTANodeView extends RectangularNodeView { element = renderCircle(node); break; case FTAAspect.AND: - element = renderAndGate(node); //not correct yet + element = renderAndGate(node); break; case FTAAspect.OR: - element = renderOrGate(node); //not correct yet + element = renderOrGate(node); break; case FTAAspect.KN: - element = renderKnGate(node, node.k as number, node.n as number); //not correct yet + element = renderKnGate(node, node.k as number, node.n as number); break; case FTAAspect.INHIBIT: - element = renderInhibitGate(node); //wrong + element = renderInhibitGate(node); break; default: element = renderRectangle(node); diff --git a/extension/src-webview/model-viewer.ts b/extension/src-webview/model-viewer.ts index 7c8d066..fca42ce 100644 --- a/extension/src-webview/model-viewer.ts +++ b/extension/src-webview/model-viewer.ts @@ -30,11 +30,3 @@ export class StpaModelViewer extends ModelViewer { } } - -export class FtaModelViewer extends ModelViewer{ - - @postConstruct() - init(): void { - - } -} \ No newline at end of file diff --git a/extension/src-webview/views-rendering.tsx b/extension/src-webview/views-rendering.tsx index 613539b..4a5fe5c 100644 --- a/extension/src-webview/views-rendering.tsx +++ b/extension/src-webview/views-rendering.tsx @@ -171,7 +171,11 @@ export function renderHexagon(node: SNode): VNode { />; } - +/** + * Creates an And-Gate for {@code node}. + * @param node The node whch should be represented by an And-Gate. + * @returns An And-Gate for {@code node}. + */ export function renderAndGate(node:SNode): VNode{ const leftX = 0; const rightX = Math.max(node.size.width, 0); @@ -187,10 +191,14 @@ export function renderAndGate(node:SNode): VNode{ />; } - +/** + * Creates an Or-Gate for {@code node}. + * @param node The node whch should be represented by an Or-Gate. + * @returns An Or-Gate for {@code node}. + */ export function renderOrGate(node:SNode): VNode{ const leftX = 0; - const rightX = Math.max(node.size.width - 5.0, 0); + const rightX = Math.max(node.size.width, 0); const midX = rightX / 2.0; const botY = Math.max(node.size.height, 0); const nearBotY = Math.max(node.size.height, 0) - (Math.max(node.size.height, 0) / 10.0); @@ -204,9 +212,14 @@ export function renderOrGate(node:SNode): VNode{ /> } +/** + * Creates an Kn-Gate for {@code node}. + * @param node The node whch should be represented by an Kn-Gate. + * @returns An Kn-Gate for {@code node}. + */ export function renderKnGate(node:SNode, k:number, n:number): VNode{ const leftX = 0; - const rightX = Math.max(node.size.width - 5.0, 0); + const rightX = Math.max(node.size.width, 0); const midX = rightX / 2.0; const botY = Math.max(node.size.height, 0); const nearBotY = Math.max(node.size.height, 0) - (Math.max(node.size.height, 0) / 10.0); @@ -225,7 +238,11 @@ export function renderKnGate(node:SNode, k:number, n:number): VNode{ ); } - +/** + * Creates an Inhibit-Gate for {@code node}. + * @param node The node whch should be represented by an Inhibit-Gate. + * @returns An Inhibit-Gate for {@code node}. + */ export function renderInhibitGate(node:SNode): VNode{ const leftX = 0; const midX = Math.max(node.size.width, 0) / 2.0 ; diff --git a/test.fta b/test.fta index b59a0c2..7be5f9a 100644 --- a/test.fta +++ b/test.fta @@ -7,6 +7,7 @@ C2 "CPU2" PS "Power supply" B "System bus" + Conditions U "In Use" @@ -19,5 +20,6 @@ G2 = G3 or B G3 = G4 and G5 G4 = C1 or PS or G6 G5 = C2 or PS or G6 -G6 = 2 of M1 M2 M3 +G6 = 2 of M1, M2, M3 + diff --git a/teststpa.stpa b/teststpa.stpa index e8156ce..7fee885 100644 --- a/teststpa.stpa +++ b/teststpa.stpa @@ -6,3 +6,9 @@ L3 "asd" Hazards H1 "" [L1] H2 "" [L2] + + +SystemConstraints +SC2 "" [H2] { + +} \ No newline at end of file From 5e7a7d611bf213ee38110fe285142bdcd1e560c7 Mon Sep 17 00:00:00 2001 From: Orhan Date: Fri, 26 May 2023 12:00:21 +0200 Subject: [PATCH 13/61] Fixed errors --- .../fta/fta-diagram-generator.ts | 4 +-- .../fta/fta-diagramServer.ts | 5 ++- .../src-language-server/fta/fta-interfaces.ts | 5 ++- .../fta/fta-layout-config.ts | 5 ++- .../src-language-server/fta/fta-module.ts | 15 ++++---- .../src-language-server/fta/fta-utils.ts | 35 +++++++++--------- .../src-language-server/fta/fta-validator.ts | 4 +-- extension/src-language-server/utils.ts | 4 +-- extension/src-webview/css/fta-theme.css | 2 +- extension/src-webview/di.config.ts | 4 +-- extension/src-webview/fta-model.ts | 6 ++-- extension/src-webview/fta-views.tsx | 36 ++++++++++++++++--- extension/src/language-extension.ts | 2 +- 13 files changed, 74 insertions(+), 53 deletions(-) diff --git a/extension/src-language-server/fta/fta-diagram-generator.ts b/extension/src-language-server/fta/fta-diagram-generator.ts index 47ef959..49b04aa 100644 --- a/extension/src-language-server/fta/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/fta-diagram-generator.ts @@ -61,7 +61,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ const ftaEdges: FTAEdge[] = []; for(const edge of ftaChildren){ if(edge.type === FTA_EDGE_TYPE){ - ftaEdges.push(edge as FTAEdge) + ftaEdges.push(edge as FTAEdge); } } @@ -157,7 +157,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ if(isTopEvent(node) || isGate(node) || isComponent(node) || isCondition(node)){ const nodeId = idCache.uniqueId(node.name, node); - let children: SModelElement[] = [ + const children: SModelElement[] = [ { type: 'label', id: idCache.uniqueId(nodeId + '.label'), diff --git a/extension/src-language-server/fta/fta-diagramServer.ts b/extension/src-language-server/fta/fta-diagramServer.ts index ededc42..174064f 100644 --- a/extension/src-language-server/fta/fta-diagramServer.ts +++ b/extension/src-language-server/fta/fta-diagramServer.ts @@ -15,10 +15,9 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { Action, DiagramServices, DiagramServer, RequestAction, RequestModelAction, ResponseAction } from 'sprotty-protocol'; +import { Action, DiagramServer, DiagramServices, RequestAction, RequestModelAction, ResponseAction } from 'sprotty-protocol'; import { UpdateViewAction } from '../actions'; -import { SetSynthesisOptionsAction, UpdateOptionsAction } from '../options/actions'; -import { DropDownOption } from '../options/option-models'; +import { UpdateOptionsAction } from '../options/actions'; import { FtaSynthesisOptions } from './fta-synthesis-options'; export class FtaDiagramServer extends DiagramServer { diff --git a/extension/src-language-server/fta/fta-interfaces.ts b/extension/src-language-server/fta/fta-interfaces.ts index 2055bbe..25712d0 100644 --- a/extension/src-language-server/fta/fta-interfaces.ts +++ b/extension/src-language-server/fta/fta-interfaces.ts @@ -1,6 +1,5 @@ -import { SNode, SEdge } from "sprotty-protocol"; -import{FTAAspect} from "./fta-model"; -import { Condition } from "../generated/ast"; +import { SEdge, SNode } from "sprotty-protocol"; +import { FTAAspect } from "./fta-model"; /** diff --git a/extension/src-language-server/fta/fta-layout-config.ts b/extension/src-language-server/fta/fta-layout-config.ts index 72d8c8e..9893d3b 100644 --- a/extension/src-language-server/fta/fta-layout-config.ts +++ b/extension/src-language-server/fta/fta-layout-config.ts @@ -1,8 +1,7 @@ import { LayoutOptions } from 'elkjs'; import { DefaultLayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; -import { SEdge, SGraph, SModelElement, SModelIndex, SNode } from 'sprotty-protocol'; +import { SGraph, SModelIndex, SNode } from 'sprotty-protocol'; import { FTANode } from './fta-interfaces'; -import { FTAAspect, FTA_NODE_TYPE } from './fta-model'; export class FtaLayoutConfigurator extends DefaultLayoutConfigurator { @@ -33,6 +32,6 @@ export class FtaLayoutConfigurator extends DefaultLayoutConfigurator { 'org.eclipse.elk.algorithm': 'layered', 'org.eclipse.elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX', 'org.eclipse.elk.spacing.portsSurrounding': '[top=10.0,left=10.0,bottom=10.0,right=10.0]' - } + }; } } \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index f7e25c8..10ec55c 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -1,19 +1,16 @@ +import ElkConstructor from 'elkjs/lib/elk.bundled'; import { createDefaultModule, createDefaultSharedModule, DefaultSharedModuleContext, inject, Module, PartialLangiumServices } from 'langium'; import { DefaultDiagramServerManager, DiagramActionNotification, LangiumSprottyServices, LangiumSprottySharedServices, SprottyDiagramServices, SprottySharedServices } from 'langium-sprotty'; -import { StpaDiagramServer } from '../stpa/stpa-diagramServer'; +import { DefaultElementFilter, ElkFactory, ElkLayoutEngine, IElementFilter, ILayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; import { DiagramOptions } from 'sprotty-protocol'; import { URI } from 'vscode-uri'; import { FtaGeneratedModule, StpaGeneratedSharedModule } from '../generated/module'; -import { FtaValidationRegistry, FtaValidator } from './fta-validator'; -import { StpaSynthesisOptions } from '../stpa/synthesis-options'; import { FtaDiagramGenerator } from './fta-diagram-generator'; -import { FtaSynthesisOptions } from './fta-synthesis-options'; import { FtaDiagramServer } from './fta-diagramServer'; -import { DefaultElementFilter, DefaultLayoutConfigurator, ElkFactory, ElkLayoutEngine, IElementFilter, ILayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; -import ElkConstructor from 'elkjs/lib/elk.bundled'; -import { StpaLayoutConfigurator } from '../stpa/layout-config'; import { FtaLayoutConfigurator } from './fta-layout-config'; +import { FtaSynthesisOptions } from './fta-synthesis-options'; +import { FtaValidationRegistry, FtaValidator } from './fta-validator'; @@ -37,13 +34,13 @@ export type FtaAddedServices = { FtaSynthesisOptions: FtaSynthesisOptions } -} +}; /** * Union of Langium default services and your custom services - use this as constructor parameter * of custom service classes. */ -export type FtaServices = LangiumSprottyServices & FtaAddedServices +export type FtaServices = LangiumSprottyServices & FtaAddedServices; /** * Dependency injection module that overrides Langium default services and contributes the diff --git a/extension/src-language-server/fta/fta-utils.ts b/extension/src-language-server/fta/fta-utils.ts index 0858934..dc25373 100644 --- a/extension/src-language-server/fta/fta-utils.ts +++ b/extension/src-language-server/fta/fta-utils.ts @@ -1,8 +1,7 @@ import { AstNode } from 'langium'; -import { AND, InhibitGate, KNGate, OR, TopEvent, isAND, isComponent, isGate, isInhibitGate, isKNGate, isOR, isTopEvent, Child, isCondition, Gate, Condition } from '../generated/ast'; +import { Gate, isAND, isComponent, isCondition, isGate, isInhibitGate, isKNGate, isOR, isTopEvent } from '../generated/ast'; import { FTAEdge, FTANode } from './fta-interfaces'; import { FTAAspect } from './fta-model'; -import { FTA_NODE_TYPE } from '../../src-webview/fta-model'; @@ -24,7 +23,7 @@ export function getTargets(node: AstNode): AstNode[] { for(const ref of node.type.child){ if(ref?.ref){targets.push(ref.ref);} //G3 = G4 AND G5 Referencen von G3: ref?.ref gelten wahrscheinlich als undefined } - if(node.type.$type == 'InhibitGate'){ + if(node.type.$type === 'InhibitGate'){ for(const ref of node.type.condition){ if(ref?.ref){targets.push(ref.ref);} } @@ -67,23 +66,23 @@ export function getAspect(node: AstNode): FTAAspect { * @returns A two dimensional array with every gate sorted into the respective category of And, Or, KN, Inhibit-Gate */ export function getAllGateTypes(everyGate: Gate[]): AstNode[][]{ - let andGates: AstNode[] = []; - let orGates: AstNode[] = []; - let kNGates: AstNode[] = []; - let inhibGates: AstNode[] = []; + const andGates: AstNode[] = []; + const orGates: AstNode[] = []; + const kNGates: AstNode[] = []; + const inhibGates: AstNode[] = []; for(const gate of everyGate){ - if(gate.type.$type == 'AND'){ + if(gate.type.$type === 'AND'){ andGates.push(gate); - }else if(gate.type.$type == 'OR'){ + }else if(gate.type.$type === 'OR'){ orGates.push(gate); - }else if(gate.type.$type == 'KNGate'){ + }else if(gate.type.$type === 'KNGate'){ kNGates.push(gate); - }else if(gate.type.$type == 'InhibitGate'){ + }else if(gate.type.$type === 'InhibitGate'){ inhibGates.push(gate); } } - let result: AstNode[][] = [andGates, orGates, kNGates, inhibGates]; + const result: AstNode[][] = [andGates, orGates, kNGates, inhibGates]; return result; } @@ -94,7 +93,7 @@ export function getAllGateTypes(everyGate: Gate[]): AstNode[][]{ */ export function setLevelsForFTANodes(nodes: FTANode[], edges: FTAEdge[]): void{ //start with the top event - let topevent: FTANode[] = [getTopEvent(nodes)]; + const topevent: FTANode[] = [getTopEvent(nodes)]; determineLevelForChildren(topevent, 0, edges, nodes); } @@ -109,7 +108,7 @@ function getTopEvent(nodes: FTANode[]): FTANode{ return node; } } - let empty = {} as FTANode; + const empty = {} as FTANode; return empty; } @@ -121,7 +120,7 @@ function getTopEvent(nodes: FTANode[]): FTANode{ * @param allNodes All nodes in the graph. */ function determineLevelForChildren(nodes: FTANode[], level: number, edges: FTAEdge[], allNodes: FTANode[]): void{ - let children: FTANode[] = []; + const children: FTANode[] = []; for(const node of nodes){ //for every node on current layer assign the level. @@ -140,7 +139,7 @@ function determineLevelForChildren(nodes: FTANode[], level: number, edges: FTAEd //If there is an inhibit gate in the next iteration/layer, then also //add Condition to children, so that they can be on the same layer as the inhibit gates. for(const node of children){ - if(node.aspect == FTAAspect.INHIBIT){ + if(node.aspect === FTAAspect.INHIBIT){ for(const edge of edges){ if(edge.sourceId === node.id){ node.level = level; @@ -155,7 +154,7 @@ function determineLevelForChildren(nodes: FTANode[], level: number, edges: FTAEd level++; //Only repeat until there is no layer below - if(children.length != 0){ + if(children.length !== 0){ determineLevelForChildren(children, level, edges, allNodes); } @@ -170,7 +169,7 @@ function determineLevelForChildren(nodes: FTANode[], level: number, edges: FTAEd */ function getChildWithID(nodes: FTANode[], id: String): FTANode{ for(const node of nodes){ - if(node.id == id){ + if(node.id === id){ return node; } } diff --git a/extension/src-language-server/fta/fta-validator.ts b/extension/src-language-server/fta/fta-validator.ts index 936aa83..d31c974 100644 --- a/extension/src-language-server/fta/fta-validator.ts +++ b/extension/src-language-server/fta/fta-validator.ts @@ -39,7 +39,7 @@ export class FtaValidator { accept('error', `Component has non-unique name '${c.name}'.`, {node: c, property: 'name'}); } componentNames.add(c.name); - }) + }); } //prevent multiple gates from having the same identifier. checkUniqueGates(model:ModelFTA, accept:ValidationAcceptor): void{ @@ -49,6 +49,6 @@ export class FtaValidator { accept('error', `Gate has non-unique name '${g.name}'.`, {node: g, property: 'name'}); } gateNames.add(g.name); - }) + }); } } diff --git a/extension/src-language-server/utils.ts b/extension/src-language-server/utils.ts index 5bcf469..4d3b1ef 100644 --- a/extension/src-language-server/utils.ts +++ b/extension/src-language-server/utils.ts @@ -15,10 +15,10 @@ * SPDX-License-Identifier: EPL-2.0 */ +import { LangiumDocument } from "langium"; import { LangiumSprottySharedServices } from "langium-sprotty"; -import { Model, ModelFTA } from "./generated/ast"; import { URI } from 'vscode-uri'; -import { LangiumDocument } from "langium"; +import { Model } from "./generated/ast"; /** * Determines the model for {@code uri}. diff --git a/extension/src-webview/css/fta-theme.css b/extension/src-webview/css/fta-theme.css index 1077843..33c0b98 100644 --- a/extension/src-webview/css/fta-theme.css +++ b/extension/src-webview/css/fta-theme.css @@ -24,7 +24,7 @@ --fta-condition-dark: #006600; --fta-and-dark: purple; --fta-or-dark: #0054A8; - --fta-kn-constraint-dark: #006161; + --fta-kn-dark: #006161; --fta-inhibit-dark: #616100; --fta-dark-node: #ffffff00; diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts index 0940b13..38ae8d1 100644 --- a/extension/src-webview/di.config.ts +++ b/extension/src-webview/di.config.ts @@ -31,7 +31,7 @@ import { sidebarModule } from './sidebar'; import { optionsModule } from './options/options-module'; import { StpaModelViewer} from './model-viewer'; import { StpaMouseListener } from './stpa-mouselistener'; -import { FTANodeView } from './fta-views'; +import { FTANodeView, PolylineArrowEdgeViewFTA } from './fta-views'; import { FTANode, FTA_EDGE_TYPE, FTA_NODE_TYPE } from './fta-model'; const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { @@ -60,7 +60,7 @@ const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => //FTA configureModelElement(context, FTA_NODE_TYPE, FTANode, FTANodeView); - configureModelElement(context, FTA_EDGE_TYPE, SEdge, PolylineArrowEdgeView); + configureModelElement(context, FTA_EDGE_TYPE, SEdge, PolylineArrowEdgeViewFTA); }); export function createSTPADiagramContainer(widgetId: string): Container { diff --git a/extension/src-webview/fta-model.ts b/extension/src-webview/fta-model.ts index dbd72f0..02476a6 100644 --- a/extension/src-webview/fta-model.ts +++ b/extension/src-webview/fta-model.ts @@ -17,10 +17,10 @@ export class FTANode extends SNode{ aspect: FTAAspect = FTAAspect.UNDEFINED; description: string = ""; - highlight?: boolean + highlight?: boolean; level?: number; - k?: number - n?: number + k?: number; + n?: number; } diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index caac257..d39a6f4 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -1,22 +1,49 @@ /** @jsx svg */ import { inject, injectable } from 'inversify'; import { VNode } from "snabbdom"; -import { RectangularNodeView, RenderingContext, SPort, svg } from 'sprotty'; +import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SEdge, SPort, svg } from 'sprotty'; import { DISymbol } from "./di.symbols"; import { FTAAspect, FTANode } from './fta-model'; -import { RenderOptionsRegistry } from "./options/render-options-registry"; +import { ColorStyleOption, RenderOptionsRegistry } from "./options/render-options-registry"; import { renderAndGate, renderCircle, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "./views-rendering"; /** Determines if path/aspect highlighting is currently on. */ let highlighting: boolean; - @injectable() -export class FTANodeView extends RectangularNodeView { +export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { @inject(DISymbol.RenderOptionsRegistry) renderOptionsRegistry: RenderOptionsRegistry; + protected renderLine(edge: SEdge, segments: Point[], context: RenderingContext): VNode { + const firstPoint = segments[0]; + let path = `M ${firstPoint.x},${firstPoint.y}`; + for (let i = 1; i < segments.length; i++) { + const p = segments[i]; + path += ` L ${p.x},${p.y}`; + } + + + const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption); + return ; + } + + protected renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] { + const p1 = segments[segments.length - 2]; + const p2 = segments[segments.length - 1]; + + const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption); + return [ + + ]; + } +} + + +@injectable() +export class FTANodeView extends RectangularNodeView { + render(node: FTANode, context: RenderingContext): VNode { @@ -50,6 +77,7 @@ export class FTANodeView extends RectangularNodeView { } return {element} {context.renderChildren(node)} diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index dbd2565..2b7f0e3 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -210,7 +210,7 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { return 'stpa-diagram'; } if(commandArgs[0] instanceof vscode.Uri && commandArgs[0].path.endsWith('.fta')){ - return 'fta-diagram' + return 'fta-diagram'; } return undefined; } From 8c8f761aaf5c26f900e34158a3246cbc3fe28b0e Mon Sep 17 00:00:00 2001 From: Orhan Date: Fri, 26 May 2023 12:28:26 +0200 Subject: [PATCH 14/61] updated visualisation --- extension/src-webview/css/fta-diagram.css | 2 +- extension/src-webview/di.config.ts | 2 +- extension/src-webview/fta-views.tsx | 3 ++- extension/src-webview/views-rendering.tsx | 4 ++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/extension/src-webview/css/fta-diagram.css b/extension/src-webview/css/fta-diagram.css index 6e806ba..f12ae64 100644 --- a/extension/src-webview/css/fta-diagram.css +++ b/extension/src-webview/css/fta-diagram.css @@ -189,7 +189,7 @@ .vscode-dark .fta-edge[aspect="5"], .vscode-dark .fta-edge-arrow[aspect="5"] { - stroke: var(--fta-kn-dark); + stroke: var(--fta-or-dark); } .vscode-dark .fta-edge[aspect="6"], diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts index 38ae8d1..60dae91 100644 --- a/extension/src-webview/di.config.ts +++ b/extension/src-webview/di.config.ts @@ -59,8 +59,8 @@ const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => configureModelElement(context, 'pre-rendered', PreRenderedElement, PreRenderedView); //FTA - configureModelElement(context, FTA_NODE_TYPE, FTANode, FTANodeView); configureModelElement(context, FTA_EDGE_TYPE, SEdge, PolylineArrowEdgeViewFTA); + configureModelElement(context, FTA_NODE_TYPE, FTANode, FTANodeView); }); export function createSTPADiagramContainer(widgetId: string): Container { diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index d39a6f4..3347707 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -28,7 +28,7 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption); return ; } - + /* protected renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] { const p1 = segments[segments.length - 2]; const p2 = segments[segments.length - 1]; @@ -38,6 +38,7 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { ]; } + */ } diff --git a/extension/src-webview/views-rendering.tsx b/extension/src-webview/views-rendering.tsx index 4a5fe5c..15db123 100644 --- a/extension/src-webview/views-rendering.tsx +++ b/extension/src-webview/views-rendering.tsx @@ -231,7 +231,7 @@ export function renderKnGate(node:SNode, k:number, n:number): VNode{ return ( - + {`${k}/${n}`} @@ -253,7 +253,7 @@ export function renderInhibitGate(node:SNode): VNode{ const highestY = 0; const d = 'M' + leftX + " " + lowY + " L " + leftX + " " + highY + " L " + midX + " " + highestY + " L " + rightX + " " + highY + - " L " + rightX + " " + lowY + " L " + midX + " " + lowestY; + " L " + rightX + " " + lowY + " L " + midX + " " + lowestY + "Z"; return Date: Wed, 14 Jun 2023 17:25:04 +0200 Subject: [PATCH 15/61] Cut Sets and Minimal Cut Sets --- extension/package.json | 62 +++- .../src-language-server/fta/bdd-generator.ts | 328 ++++++++++++++++++ .../fta/fta-diagram-generator.ts | 27 +- .../src-language-server/fta/fta-interfaces.ts | 14 + .../fta/fta-message-handler.ts | 36 ++ .../src-language-server/fta/fta-model.ts | 2 + .../src-language-server/fta/fta-module.ts | 11 +- extension/src-language-server/main.ts | 2 + extension/src-webview/css/fta-diagram.css | 14 +- extension/src-webview/css/fta-theme.css | 3 +- extension/src-webview/fta-model.ts | 20 +- extension/src-webview/fta-views.tsx | 13 +- extension/src-webview/views-rendering.tsx | 5 +- extension/src/extension.ts | 2 +- extension/src/language-extension.ts | 52 ++- test.fta | 4 +- teststpa.stpa | 10 - 17 files changed, 546 insertions(+), 59 deletions(-) create mode 100644 extension/src-language-server/fta/bdd-generator.ts create mode 100644 extension/src-language-server/fta/fta-message-handler.ts diff --git a/extension/package.json b/extension/package.json index a5eb3b2..21b26fc 100644 --- a/extension/package.json +++ b/extension/package.json @@ -107,7 +107,7 @@ "category": "PASTA Diagram" }, { - "command": "stpa.contextTable.open", + "command": "pasta.contextTable.open", "title": "Show Context Tables", "icon": "$(table)", "category": "Context Table" @@ -181,6 +181,18 @@ "command": "stpa.IDs.redo", "title": "Executes the redo action", "category": "STPA ID Enforcement" + }, + { + "command": "pasta.generate.cutSets", + "title": "Generates the cut sets", + "icon": "$(symbol-structure)", + "category": "FTA Diagram" + }, + { + "command": "pasta.generate.minimalCutSets", + "title": "Generates the minimal cut sets", + "icon": "$(symbol-structure)", + "category": "FTA Diagram" } ], "menus": { @@ -190,11 +202,11 @@ "when": "editorLangId == 'stpa' || editorLangId == 'fta'" }, { - "command": "stpa.contextTable.open", + "command": "pasta.contextTable.open", "when": "editorLangId == 'stpa'" }, { - "command": "stpa.contextTable.open", + "command": "pasta.contextTable.open", "when": "editorLangId == 'stpa'" }, { @@ -244,6 +256,14 @@ { "command": "stpa.checks.checkSafetyRequirementsForUCAs", "when": "editorLangId == 'stpa'" + }, + { + "command": "pasta.generate.cutSets", + "when": "editorLangId == 'fta'" + }, + { + "command": "pasta.generate.minimalCutSets", + "when": "editorLangId == 'fta'" } ], "editor/context": [ @@ -253,13 +273,23 @@ "group": "navigation" }, { - "command": "stpa.contextTable.open", + "command": "pasta.contextTable.open", "when": "editorLangId == 'stpa'", "group": "navigation" }, { "submenu": "stpa.checks", "group": "checks" + }, + { + "command": "pasta.generate.cutSets", + "when": "editorLangId == 'fta'", + "group": "navigation" + }, + { + "command": "pasta.generate.minimalCutSets", + "when": "editorLangId == 'fta'", + "group": "navigation" } ], "stpa.checks": [ @@ -291,9 +321,19 @@ "group": "navigation" }, { - "command": "stpa.contextTable.open", + "command": "pasta.contextTable.open", "when": "editorLangId == 'stpa'", "group": "navigation" + }, + { + "command": "pasta.generate.cutSets", + "when": "editorLangId == 'fta'", + "group": "navigation" + }, + { + "command": "pasta.generate.minimalCutSets", + "when": "editorLangId == 'fta'", + "group": "navigation" } ], "explorer/context": [ @@ -303,9 +343,19 @@ "group": "navigation" }, { - "command": "stpa.contextTable.open", + "command": "pasta.contextTable.open", "when": "resourceExtname == '.stpa'", "group": "navigation" + }, + { + "command": "pasta.generate.cutSets", + "when": "resourceExtname == '.fta'", + "group": "navigation" + }, + { + "command": "pasta.generate.minimalCutSets", + "when": "resourceExtname == '.fta'", + "group": "navigation" } ] }, diff --git a/extension/src-language-server/fta/bdd-generator.ts b/extension/src-language-server/fta/bdd-generator.ts new file mode 100644 index 0000000..216f310 --- /dev/null +++ b/extension/src-language-server/fta/bdd-generator.ts @@ -0,0 +1,328 @@ +import { FTAEdge, FTANode } from './fta-interfaces'; +import { FTAAspect } from "./fta-model"; + + +export class BDDGenerator{ + + + determineMinimalCutSet(allNodes:FTANode[], allEdges:FTAEdge[]):FTANode[][]{ + const bdd = this.generateCutSets(allNodes, allEdges); + + //Cut sets are minimal if, when any basic event is removed from the set, the remaining events collectively are no longer a cut set. + //Check every innerList + //If inner list contains another array from the bdd array, remove innerList because it cant be a minimal cut set + const minimalCutSet = bdd.filter(innerList => { + return this.checkIfMinimalCutSet(innerList, bdd); //if this condition is true then the innerList is a minimal cut set + }); + + return minimalCutSet; + } + + /** + * Takes a list and all cut sets and checks if the given list is a minimal cut set. + * @param innerList The list we want to check. + * @param bdd All Cut Sets of the Fault Tree + * @returns True if the given list is a minimal cut set or false if is not. + */ + checkIfMinimalCutSet(innerList:FTANode[], bdd:FTANode[][]):boolean{ + for(const list of bdd){ + if(list.every(e=>innerList.includes(e)) && innerList !== list){ + return false; + } + } + + + return true; + } + /** + * Takes the Fault Tree and returns a two-dimensional array of FTANodes where every inner list resembles a cut set. + * @param allNodes All Nodes in the graph. + * @param allEdges All Edges in the graph. + * @returns A list of lists that that contains every cut set of the given Fault Tree. + */ + generateCutSets(allNodes:FTANode[], allEdges:FTAEdge[]):FTANode[][]{ + + + + //Algorithm idea: + //Start from the top event. + //Get the only child of top event (will always be only one). + //Calculate all children of the node. + //Evaluate every single child and their childs recursively. + //Depending on the type of the node process the results of the children differently. + + //Order components by level from top to bottom for a smaller BDD. + allNodes.sort(this.sortByLevel); + + //Evaluate the child of the top event and recursively the entire Fault Tree. + const unprocressedCutSets = this.evaluate(this.getChildOfTopEvent(allNodes, allEdges), allNodes, allEdges); + + //In the case that two gates share the same child, remove duplicates from all innerLists. [[C,U,U]] -> [[C,U]] + const tempCutSets:FTANode[][] = []; + for(const innerList of unprocressedCutSets){ + const filteredList = innerList.filter((e,i) => innerList.indexOf(e) === i); //indexOf only returns the first index of the element in the list. + tempCutSets.push(filteredList); + } + + //In case there are two inner lists with the same elements, remove the duplicates. [[C,U], [U,C]] -> [[C,U]] + const cutSets = tempCutSets.filter((e,i) => this.indexOfArray(e, tempCutSets) === i); + + return cutSets; + + } + + /** + * Takes a single node and returns it evaluation depending on the node type and number of children. This function is called recursively for all children. + * @param node The node we want to evaluate. + * @param allNodes All Nodes in the graph. + * @param allEdges All Edges in the graph. + * @returns A list of lists that is the result of evaluating the given node. + */ + evaluate(node:FTANode, allNodes: FTANode[], allEdges: FTAEdge[]): FTANode[][]{ + let result:FTANode[][] = []; + + // we start with the top-most gate(child of topevent) and get all its children. + const children = this.getAllChildrenOfNode(node, allNodes, allEdges); + + //if the node is an and/inhibit-gate we want to evaluate all children and concatenate all inner lists of one child with another. + if(node.aspect === FTAAspect.AND || node.aspect === FTAAspect.INHIBIT){ + for(const child of children){ + if(child.aspect === FTAAspect.COMPONENT || child.aspect === FTAAspect.CONDITION){ + result = this.f([[child]], result); + }else{ + result = this.f(this.evaluate(child, allNodes, allEdges), result); + } + } + //if the node is an or-gate we want to evaluate all children and add every single inner list to the result. + }else if(node.aspect === FTAAspect.OR){ + for(const child of children){ + if(child.aspect === FTAAspect.COMPONENT){ + const orList = [child]; + result.push(orList); + }else{ + for(const list of this.evaluate(child, allNodes, allEdges)){ //push every inner list of the child gate. + result.push(list); + } + } + } + //if the node is a kN-gate we want to get every combinations of the children with length k and after that evaluate the gates in the list. + }else if(node.aspect === FTAAspect.KN){ + const k = node.k as number; + + //Example: With Children:[M1,M2,G1] -> [[M1,M2],[M1,G1],[M2,G1]] . + const combinations = this.getAllCombinations(children, k); + //Now we want to evaluate G1 (e.g evaluation(G1) = [[C]]). + //Our result list should look like this -> [[M1,M2], [M1,C], [M2,C]]. + for(const comb of combinations){ + if(comb.some(e => e.aspect === FTAAspect.AND || e.aspect === FTAAspect.INHIBIT || e.aspect === FTAAspect.OR || e.aspect === FTAAspect.KN)){ + const evaluatedLists = this.evaluateGateInCombinationList(comb, allNodes, allEdges); + for(const list of evaluatedLists){ + result.push(list); + } + }else{ + result.push(comb); + } + } + } + + return result; + + } + + /** + * Takes a list of components, conditions and gates and then removes the gates and inserts its evaluation in the list. This can result in multiple lists. + * @param innerList The list we want to evaluate. + * @param allNodes All Nodes in the graph. + * @param allEdges All Edges in the graph. + * @returns A list of lists that is the result of inserting the evaluation of the gates in the given list. + */ + evaluateGateInCombinationList(innerList: FTANode[], allNodes:FTANode[], allEdges:FTAEdge[]):FTANode[][]{ + + let result:FTANode[][] = []; + const restList:FTANode[] = innerList; + + for(const element of restList){ + //when the element is a gate. + if(element.aspect === FTAAspect.AND || element.aspect === FTAAspect.INHIBIT || element.aspect === FTAAspect.OR || element.aspect === FTAAspect.KN){ + //cut out the gate from the rest list. + const index = restList.indexOf(element); + restList.splice(index, 1); + //and push the evaluation of the gate into the result list. + const tempLists = this.f(this.evaluate(element, allNodes, allEdges), result); + for(const list of tempLists){ + result.push(list); + } + + } + } + //concatenate every element of the rest list with the result (should only be components/conditions). + for(const list of restList){ + result = this.f([[list]], result); + } + + + return result; + + } + + /** + * Gets all combinations of the elements in the given list with length k. + * @param nodes The list of nodes we want the combinations of. + * @param k The number of elements we want in an innerList. + * @returns the combinations of the elements in the given list with length k. + */ + getAllCombinations(nodes:FTANode[], k:number):FTANode[][]{ + const combinations:FTANode[][] = []; + + if (k > nodes.length || k <= 0) { + return []; + } + if (k === nodes.length) { + return [nodes]; + } + if(k===1){ + for(let i = 0; i b.level){ + return 1; + }else if(a.id < b.id){ + return -1; + }else if(a.id > b.id){ + return 1; + } + return 0; + } + return 0; + } + /** + * Given all Nodes this method returns the first and only child of the topevent. + * @param nodes All FtaNodes in the graph. + * @param id All FTAEdges in the graph. + * @returns the child of the topevent. + */ + getChildOfTopEvent(allNodes:FTANode[], allEdges:FTAEdge[]): FTANode{ + for(const node of allNodes){ + for(const edge of allEdges){ + if(node.level === 0 && edge.sourceId === node.id){ + return this.getNodeWithID(allNodes, edge.targetId); + } + } + } + + const empty = {} as FTANode; + return empty; + } + /** + * Concatenates every inner List of two two-dimensional arrays . + * @param a The first two-dimensional FTANode array. + * @param b The second two-dimensional FTANode array. + * @returns a two-dimensional array of type FTANode where every innerList of both arrays is concatenated. + */ + f(a:FTANode[][], b:FTANode[][]):FTANode[][]{ + const result: FTANode[][] = []; + + if(a.length === 0){ + return b; + } + if(b.length === 0){ + return a; + } + + for (const innerA of a) { + for (const innerB of b) { + result.push(innerA.concat(innerB)); + } + } + + return result; + + } + /** + * Checks if array a and b are equal, + * @param a The first array we want to compare. + * @param b The second array we want to compaare. + * @returns True if they are equal and false if not. + */ + arrayEquals(a:FTANode[], b:FTANode[]):boolean{ + const sortedA = a.sort((x,y) => this.sortByLevel(x,y)); + const sortedB = b.sort((x,y) => this.sortByLevel(x,y)); + return a.length === b.length && sortedA.every((e,i) => e === sortedB[i]); + } + + /** + * Gets the index of a list in a two-dimensional list of FTANodes. + * @param a The list we want the index of. + * @param b The two-dimensional list of FTANodes we want to search in. + * @returns the index of the list. + */ + indexOfArray(a:FTANode[], b:FTANode[][]):number{ + let i = 0; + for(const list of b){ + if(this.arrayEquals(a, list)){ + break; + } + i++; + } + return i; + } + +} diff --git a/extension/src-language-server/fta/fta-diagram-generator.ts b/extension/src-language-server/fta/fta-diagram-generator.ts index 49b04aa..0c43acf 100644 --- a/extension/src-language-server/fta/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/fta-diagram-generator.ts @@ -4,12 +4,15 @@ import { SLabel, SModelElement, SModelRoot } from 'sprotty-protocol'; import { ModelFTA, isComponent, isCondition, isGate, isKNGate, isTopEvent } from '../generated/ast'; import { FTAEdge, FTANode } from './fta-interfaces'; import { FTA_EDGE_TYPE, FTA_NODE_TYPE, PARENT_TYPE } from './fta-model'; -import { FtaServices } from './fta-module'; import { getAllGateTypes, getAspect, getTargets, setLevelsForFTANodes } from './fta-utils'; +import { FtaServices } from './fta-module'; +//import { determineMinimalCutSet, generateCutSets } from './bdd-generator'; export class FtaDiagramGenerator extends LangiumDiagramGenerator{ + allNodes:FTANode[]; + allEdges:FTAEdge[]; constructor(services: FtaServices){ super(services); } @@ -65,9 +68,12 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ } } - // give the top event the level 0 - setLevelsForFTANodes(ftaNodes, ftaEdges); + this.allNodes = ftaNodes; + this.allEdges = ftaEdges; + // give the top event the level 0 + setLevelsForFTANodes(ftaNodes, ftaEdges); + return { type: 'graph', id: 'root', @@ -81,12 +87,19 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ }; } + + public getNodes():FTANode[]{ + return this.allNodes; + } + public getEdges():FTAEdge[]{ + return this.allEdges; + } /** - * Generates a node and the edges for the given {@code node}. - * @param node FTA component for which a node and edges should be generated. + * Generates the edges for the given {@code node}. + * @param node FTA component for which edges should be generated. * @param args GeneratorContext of the FTA model. - * @returns A node representing {@code node} and edges representing the references {@code node} contains. + * @returns Edges representing the references {@code node} contains. */ private generateAspectWithEdges(node: AstNode, args: GeneratorContext): SModelElement[] { const elements: SModelElement[] = this.generateEdgesForFTANode(node, args); @@ -106,7 +119,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ // for every reference an edge is created const targets = getTargets(node); for (const target of targets) { - const targetId = idCache.getId(target); // g3: - undefined + const targetId = idCache.getId(target); const edgeId = idCache.uniqueId(`${sourceId}:-:${targetId}`, undefined); if (sourceId && targetId) { const e = this.generateFTAEdge(edgeId, sourceId, targetId, '', args); diff --git a/extension/src-language-server/fta/fta-interfaces.ts b/extension/src-language-server/fta/fta-interfaces.ts index 25712d0..4fff483 100644 --- a/extension/src-language-server/fta/fta-interfaces.ts +++ b/extension/src-language-server/fta/fta-interfaces.ts @@ -20,4 +20,18 @@ export interface FTANode extends SNode{ */ export interface FTAEdge extends SEdge { highlight?: boolean +} + +/** + * Node representing a system component in the BDD. + */ +export interface BDDNode extends SNode { + level?: number +} + +/** + * Edge representing component failure in the BDD. + */ +export interface BDDEdge extends SEdge { + fail?: boolean } \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts new file mode 100644 index 0000000..b53092d --- /dev/null +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -0,0 +1,36 @@ +import { Connection, URI } from "vscode-languageserver"; +import { FtaServices } from './fta-module'; +import { LangiumSprottySharedServices } from "langium-sprotty"; +import { FtaDiagramGenerator } from "./fta-diagram-generator"; + + +let lastUri: URI; + +export function addFTANotificationHandler(connection: Connection, ftaServices: FtaServices, sharedServices: LangiumSprottySharedServices): void { + addGenerateCutSetsHandler(connection, ftaServices); + addGenerateMinimalCutSetsHandler(connection, ftaServices); +} + +function addGenerateCutSetsHandler(connection: Connection, ftaServices: FtaServices):void{ + connection.onRequest('generate/getCutSets', uri =>{ + lastUri = uri; + const diagramGenerator = (ftaServices.diagram.DiagramGenerator) as FtaDiagramGenerator; + const nodes = diagramGenerator.getNodes(); + const edges = diagramGenerator.getEdges(); + const cutSets = ftaServices.bdd.Bdd.generateCutSets(nodes, edges); + return cutSets; + }); + +} + +function addGenerateMinimalCutSetsHandler(connection:Connection, ftaServices: FtaServices):void{ + connection.onRequest('generate/getMinimalCutSets', uri =>{ + lastUri = uri; + const diagramGenerator = (ftaServices.diagram.DiagramGenerator) as FtaDiagramGenerator; + const nodes = diagramGenerator.getNodes(); + const edges = diagramGenerator.getEdges(); + const minimalCutSets = ftaServices.bdd.Bdd.determineMinimalCutSet(nodes, edges); + return minimalCutSets; + }); + +} \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-model.ts b/extension/src-language-server/fta/fta-model.ts index 08f12e6..03fce88 100644 --- a/extension/src-language-server/fta/fta-model.ts +++ b/extension/src-language-server/fta/fta-model.ts @@ -4,6 +4,8 @@ export const FTA_NODE_TYPE = 'node:fta'; export const PARENT_TYPE= 'node:parent'; export const EDGE_TYPE = 'edge'; export const FTA_EDGE_TYPE = 'edge:fta'; +export const BDD_NODE_TYPE = 'node:bdd'; +export const BDD_EDGE_TYPE = 'edge:bdd'; /** diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index 10ec55c..4b04f14 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -11,6 +11,8 @@ import { FtaDiagramServer } from './fta-diagramServer'; import { FtaLayoutConfigurator } from './fta-layout-config'; import { FtaSynthesisOptions } from './fta-synthesis-options'; import { FtaValidationRegistry, FtaValidator } from './fta-validator'; +import { BDDGenerator } from './bdd-generator'; + @@ -24,7 +26,7 @@ import { FtaValidationRegistry, FtaValidator } from './fta-validator'; export type FtaAddedServices = { validation: { FtaValidator: FtaValidator; - } + }, layout: { ElkFactory: ElkFactory, ElementFilter: IElementFilter, @@ -32,8 +34,10 @@ export type FtaAddedServices = { }, options: { FtaSynthesisOptions: FtaSynthesisOptions + }, + bdd: { + Bdd: BDDGenerator } - }; /** @@ -64,6 +68,9 @@ export const FtaModule: Module new FtaSynthesisOptions() }, + bdd:{ + Bdd: services => new BDDGenerator() + } }; export const ftaDiagramServerFactory = diff --git a/extension/src-language-server/main.ts b/extension/src-language-server/main.ts index 1204495..ef48d23 100644 --- a/extension/src-language-server/main.ts +++ b/extension/src-language-server/main.ts @@ -22,6 +22,7 @@ import { createConnection, ProposedFeatures } from 'vscode-languageserver/node'; import { addSTPANotificationHandler } from './stpa/message-handler'; import { addNotificationHandler } from './handler'; import { createServices } from './module'; +import { addFTANotificationHandler } from './fta/fta-message-handler'; // Create a connection to the client const connection = createConnection(ProposedFeatures.all); @@ -34,6 +35,7 @@ startLanguageServer(shared); addDiagramHandler(connection, shared); addSTPANotificationHandler(connection, stpa, shared); +addFTANotificationHandler(connection, fta, shared); addNotificationHandler(connection, shared); // handle configuration changes for the validation checks diff --git a/extension/src-webview/css/fta-diagram.css b/extension/src-webview/css/fta-diagram.css index f12ae64..ea3a119 100644 --- a/extension/src-webview/css/fta-diagram.css +++ b/extension/src-webview/css/fta-diagram.css @@ -199,7 +199,8 @@ .vscode-dark .print-node { fill: var(--fta-dark-node); - stroke: var(--vscode-editor-foreground); + stroke: var(--fta-light-node); + stroke-width: 2; } .vscode-dark .sprotty-node { @@ -325,9 +326,8 @@ stroke: black; } - - - -.fta-node { - stroke: black; -} \ No newline at end of file +.fta-text{ + fill: var(--fta-kn-dark); + stroke-width: 0.5; + font-size: 10px; +} diff --git a/extension/src-webview/css/fta-theme.css b/extension/src-webview/css/fta-theme.css index 33c0b98..e588271 100644 --- a/extension/src-webview/css/fta-theme.css +++ b/extension/src-webview/css/fta-theme.css @@ -19,6 +19,7 @@ /* Colors for fta aspects */ + --fta-topevent-dark: #AD0000; --fta-component-dark: #A85400; --fta-condition-dark: #006600; @@ -37,5 +38,5 @@ --fta-or-light: #66b3ff; --fta-kn-light: #00cccc; --fta-inhibit-light: #cccc00; - + } diff --git a/extension/src-webview/fta-model.ts b/extension/src-webview/fta-model.ts index 02476a6..bdf3fe3 100644 --- a/extension/src-webview/fta-model.ts +++ b/extension/src-webview/fta-model.ts @@ -47,13 +47,19 @@ export enum FTAAspect { UNDEFINED } + /** - * Possible edge directions. + * Node representing a system component in the BDD. */ -export enum EdgeDirection { - UP, - DOWN, - LEFT, - RIGHT, - UNDEFINED +export class BDDNode extends SNode { + level?: number; + static readonly DEFAULT_FEATURES = [connectableFeature, selectFeature, + layoutContainerFeature, fadeFeature, hoverFeedbackFeature, popupFeature]; +} + +/** + * Edge representing component failure in the BDD. + */ +export class BDDEdge extends SEdge { + fail?: boolean; } \ No newline at end of file diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index 3347707..c4f6dec 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -4,7 +4,7 @@ import { VNode } from "snabbdom"; import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SEdge, SPort, svg } from 'sprotty'; import { DISymbol } from "./di.symbols"; import { FTAAspect, FTANode } from './fta-model'; -import { ColorStyleOption, RenderOptionsRegistry } from "./options/render-options-registry"; +import { ColorStyleOption, RenderOptionsRegistry } from './options/render-options-registry'; import { renderAndGate, renderCircle, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "./views-rendering"; @@ -26,7 +26,7 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption); - return ; + return ; } /* protected renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] { @@ -45,8 +45,12 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { @injectable() export class FTANodeView extends RectangularNodeView { + @inject(DISymbol.RenderOptionsRegistry) renderOptionsRegistry: RenderOptionsRegistry; + render(node: FTANode, context: RenderingContext): VNode { - + const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption); + const printNode = colorStyle == "black & white"; + const coloredNode = colorStyle == "colorful"; // create the element based on the aspect of the node let element: VNode; @@ -77,8 +81,9 @@ export class FTANodeView extends RectangularNodeView { break; } return {element} {context.renderChildren(node)} diff --git a/extension/src-webview/views-rendering.tsx b/extension/src-webview/views-rendering.tsx index 15db123..fa5abdb 100644 --- a/extension/src-webview/views-rendering.tsx +++ b/extension/src-webview/views-rendering.tsx @@ -16,9 +16,8 @@ */ /** @jsx svg */ -import { h, VNode } from 'snabbdom'; +import { VNode } from 'snabbdom'; import { SNode, svg } from 'sprotty'; -import * as path from 'path'; /** * Creates a circle for {@code node}. @@ -231,7 +230,7 @@ export function renderKnGate(node:SNode, k:number, n:number): VNode{ return ( - + {`${k}/${n}`} diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 48ebba6..7081c3d 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -30,7 +30,7 @@ export function activate(context: vscode.ExtensionContext): void { async (uri: string) => { // generate and send back the LTLs based on the STPA UCAs await extension.lsReady; - const formulas: {formula: string, text: string, ucaId: string}[] = await extension.languageClient.sendRequest('modelChecking/generateLTL', uri); + const formulas: {formula: string, text: string, ucaId: string}[] = await extension.languageClient.sendRequest('modelChecking/generateLTL', uri); return formulas; } )); diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index 2b7f0e3..99cfbaa 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -26,6 +26,7 @@ import { UpdateViewAction } from './actions'; import { ContextTablePanel } from './context-table-panel'; import { StpaFormattingEditProvider } from './stpa-formatter'; import { StpaLspWebview } from './wview'; +import { FTANode } from '../src-language-server/fta/fta-interfaces'; export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { @@ -66,14 +67,6 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { editor.revealRange(editor.selection, vscode.TextEditorRevealType.InCenter); } }); - //Active text editor changed - // vscode.window.onDidChangeActiveTextEditor(activeEditor => { - // if(activeEditor){ - // Change extension Prefix based on command arg - //Change diagram type based on command arg - // this.getDiagramType(); - // } - // }); // textdocument has changed vscode.workspace.onDidChangeTextDocument(changeEvent => { this.handleTextChangeEvent(changeEvent); }); @@ -180,7 +173,50 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { this.ignoreNextTextChange = true; vscode.commands.executeCommand("redo"); }) + ); + this.context.subscriptions.push( + vscode.commands.registerCommand(this.extensionPrefix + '.generate.cutSets', async (...commandArgs: any[]) =>{ + this.lastUri = (commandArgs[0] as vscode.Uri).toString(); + const cutSets:FTANode[][] = await this.languageClient.sendRequest('generate/getCutSets', this.lastUri); + + const outputCutSets = vscode.window.createOutputChannel("All cut sets"); + outputCutSets.append("Cut sets: " + "\n" + this.toString(cutSets)); + outputCutSets.show(); + + }) ); + this.context.subscriptions.push( + vscode.commands.registerCommand(this.extensionPrefix + '.generate.minimalCutSets', async (...commandArgs: any[]) =>{ + this.lastUri = (commandArgs[0] as vscode.Uri).toString(); + const minimalCutSets:FTANode[][] = await this.languageClient.sendRequest('generate/getMinimalCutSets', this.lastUri); + + const outputMinimalCutSets = vscode.window.createOutputChannel("All minimal cut sets"); + outputMinimalCutSets.append("Minimal cut sets: " + "\n" + this.toString(minimalCutSets)); + outputMinimalCutSets.show(); + }) + ); + + + } + + protected toString(cutSets:FTANode[][]):string{ + let result = "[" ; + for(const set of cutSets){ + result = result + "["; + for(const element of set){ + result = result + element.id; + if(set.indexOf(element) === set.length - 1){ + break; + } + result = result + ","; + } + result = result + "]"; + if(cutSets.indexOf(set) !== cutSets.length -1){ + result = result + ","; + } + } + result = result + "]"; + return result; } /** diff --git a/test.fta b/test.fta index 7be5f9a..b584dc7 100644 --- a/test.fta +++ b/test.fta @@ -16,10 +16,8 @@ TopEvent Gates G1 = U inhibits G2 -G2 = G3 or B +G2 = G3 or B G3 = G4 and G5 G4 = C1 or PS or G6 G5 = C2 or PS or G6 G6 = 2 of M1, M2, M3 - - diff --git a/teststpa.stpa b/teststpa.stpa index 7fee885..13a29e8 100644 --- a/teststpa.stpa +++ b/teststpa.stpa @@ -2,13 +2,3 @@ Losses L1 "test" L2 "FSDFA" L3 "asd" - -Hazards -H1 "" [L1] -H2 "" [L2] - - -SystemConstraints -SC2 "" [H2] { - -} \ No newline at end of file From a9d0213e1bc90a9e03a68e45dc3d4a3bb865a205 Mon Sep 17 00:00:00 2001 From: Orhan Date: Wed, 14 Jun 2023 17:34:34 +0200 Subject: [PATCH 16/61] quick fix warning --- extension/src-language-server/fta/fta-module.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index 4b04f14..16e7787 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -69,7 +69,7 @@ export const FtaModule: Module new FtaSynthesisOptions() }, bdd:{ - Bdd: services => new BDDGenerator() + Bdd: () => new BDDGenerator() } }; From 9fd5003990c4f64c083ce84288f71e7693aea589 Mon Sep 17 00:00:00 2001 From: Orhan Date: Sun, 2 Jul 2023 15:29:12 +0200 Subject: [PATCH 17/61] Generate and highlight cut sets --- extension/package.json | 20 +---- ...d-generator.ts => fta-cutSet-generator.ts} | 87 ++++++++++++++++--- .../fta/fta-diagram-generator.ts | 5 +- .../fta/fta-message-handler.ts | 6 +- .../src-language-server/fta/fta-model.ts | 1 - .../src-language-server/fta/fta-module.ts | 6 +- extension/src-webview/di.config.ts | 5 +- extension/src-webview/di.symbols.ts | 1 + extension/src-webview/fta-model.ts | 2 +- extension/src-webview/fta-views.tsx | 25 ++++-- extension/src-webview/helper-methods.ts | 79 ++++++++++++++++- extension/src-webview/options/actions.ts | 43 ++++++++- .../src-webview/options/cut-set-panel.tsx | 48 ++++++++++ .../src-webview/options/cut-set-registry.ts | 83 ++++++++++++++++++ .../src-webview/options/options-module.ts | 11 ++- .../src-webview/options/options-renderer.tsx | 15 +++- .../options/render-options-registry.ts | 12 ++- extension/src-webview/views.tsx | 17 ++-- extension/src/actions.ts | 42 +++++++++ extension/src/language-extension.ts | 73 ++++++++++------ extension/src/wview.ts | 15 +++- test.fta | 18 +++- teststpa.stpa | 86 +++++++++++++++++- 23 files changed, 610 insertions(+), 90 deletions(-) rename extension/src-language-server/fta/{bdd-generator.ts => fta-cutSet-generator.ts} (81%) create mode 100644 extension/src-webview/options/cut-set-panel.tsx create mode 100644 extension/src-webview/options/cut-set-registry.ts diff --git a/extension/package.json b/extension/package.json index 21b26fc..9721001 100644 --- a/extension/package.json +++ b/extension/package.json @@ -184,15 +184,13 @@ }, { "command": "pasta.generate.cutSets", - "title": "Generates the cut sets", - "icon": "$(symbol-structure)", - "category": "FTA Diagram" + "title": "Generate the cut sets", + "category": "Cut Sets" }, { "command": "pasta.generate.minimalCutSets", - "title": "Generates the minimal cut sets", - "icon": "$(symbol-structure)", - "category": "FTA Diagram" + "title": "Generate the minimal cut sets", + "category": "Cut Sets" } ], "menus": { @@ -324,16 +322,6 @@ "command": "pasta.contextTable.open", "when": "editorLangId == 'stpa'", "group": "navigation" - }, - { - "command": "pasta.generate.cutSets", - "when": "editorLangId == 'fta'", - "group": "navigation" - }, - { - "command": "pasta.generate.minimalCutSets", - "when": "editorLangId == 'fta'", - "group": "navigation" } ], "explorer/context": [ diff --git a/extension/src-language-server/fta/bdd-generator.ts b/extension/src-language-server/fta/fta-cutSet-generator.ts similarity index 81% rename from extension/src-language-server/fta/bdd-generator.ts rename to extension/src-language-server/fta/fta-cutSet-generator.ts index 216f310..6e2663a 100644 --- a/extension/src-language-server/fta/bdd-generator.ts +++ b/extension/src-language-server/fta/fta-cutSet-generator.ts @@ -2,7 +2,7 @@ import { FTAEdge, FTANode } from './fta-interfaces'; import { FTAAspect } from "./fta-model"; -export class BDDGenerator{ +export class CutSetGenerator{ determineMinimalCutSet(allNodes:FTANode[], allEdges:FTAEdge[]):FTANode[][]{ @@ -44,19 +44,25 @@ export class BDDGenerator{ - //Algorithm idea: + //Idea: //Start from the top event. - //Get the only child of top event (will always be only one). - //Calculate all children of the node. - //Evaluate every single child and their childs recursively. + //Get the only child of top event (will always be only one) as our starting node. + //Calculate all children of the node and evaluate them. + //In the evaluation we check if the child has children too and do the same recursively until the children are components. //Depending on the type of the node process the results of the children differently. //Order components by level from top to bottom for a smaller BDD. allNodes.sort(this.sortByLevel); + //When there is no gate, return the component + const startingNode = this.getChildOfTopEvent(allNodes, allEdges); + if(startingNode.aspect === FTAAspect.COMPONENT){ + return [[startingNode]]; + } //Evaluate the child of the top event and recursively the entire Fault Tree. - const unprocressedCutSets = this.evaluate(this.getChildOfTopEvent(allNodes, allEdges), allNodes, allEdges); + const unprocressedCutSets = this.evaluate(startingNode, allNodes, allEdges); + //Final duplication checks: //In the case that two gates share the same child, remove duplicates from all innerLists. [[C,U,U]] -> [[C,U]] const tempCutSets:FTANode[][] = []; for(const innerList of unprocressedCutSets){ @@ -83,6 +89,7 @@ export class BDDGenerator{ // we start with the top-most gate(child of topevent) and get all its children. const children = this.getAllChildrenOfNode(node, allNodes, allEdges); + if(children.length === 0){return result;}; //if the node is an and/inhibit-gate we want to evaluate all children and concatenate all inner lists of one child with another. if(node.aspect === FTAAspect.AND || node.aspect === FTAAspect.INHIBIT){ @@ -105,12 +112,24 @@ export class BDDGenerator{ } } } + //Above we say an or-gate fails when exactly one child fails but multiple children can fail too. + result = this.generatePowerSet(result); + result.splice(0,1); // Remove empty set + + //if the node is a kN-gate we want to get every combinations of the children with length k and after that evaluate the gates in the list. }else if(node.aspect === FTAAspect.KN){ const k = node.k as number; + const n = node.n as number; - //Example: With Children:[M1,M2,G1] -> [[M1,M2],[M1,G1],[M2,G1]] . - const combinations = this.getAllCombinations(children, k); + //Example: With Children:[M1,M2,G1] and k=2 -> [[M1,M2],[M1,G1],[M2,G1]] . + const combinations:FTANode[][]=[]; + for(let i = k; i<=n; i++){ + for(const comb of this.getAllCombinations(children, i)){ + combinations.push(comb); + } + } + //Now we want to evaluate G1 (e.g evaluation(G1) = [[C]]). //Our result list should look like this -> [[M1,M2], [M1,C], [M2,C]]. for(const comb of combinations){ @@ -124,7 +143,7 @@ export class BDDGenerator{ } } } - + return result; } @@ -141,14 +160,17 @@ export class BDDGenerator{ let result:FTANode[][] = []; const restList:FTANode[] = innerList; - for(const element of restList){ + for(let i = 0; i { + return theArray.reduce((subsets: FTANode[][], values: FTANode[]) => { + const newSubsets: FTANode[][] = []; + + + for (const subset of subsets) { + let newSet = values.concat(...subset); + newSet = newSet.filter((e,i) => newSet.indexOf(e) === i); + if(this.indexOfArray(newSet, subsets) === -1 && this.indexOfArray(newSet, newSubsets) === -1){ + newSubsets.push(newSet); + } + + } + + + return subsets.concat(newSubsets); + }, [[]]); + }; + + return getPowerSet(sets); + } + + /** * Gets a node with its id from all nodes. * @param nodes All FtaNodes in the graph. @@ -250,6 +301,7 @@ export class BDDGenerator{ return 1; } return 0; + } return 0; } @@ -289,7 +341,13 @@ export class BDDGenerator{ for (const innerA of a) { for (const innerB of b) { - result.push(innerA.concat(innerB)); + //Add only unique sets + let newSet = innerA.concat(innerB); + newSet = newSet.filter((e,i) => newSet.indexOf(e) === i); + if(this.indexOfArray(newSet, result) === -1){ + result.push(newSet); + } + } } @@ -309,7 +367,7 @@ export class BDDGenerator{ } /** - * Gets the index of a list in a two-dimensional list of FTANodes. + * Gets the index of a list in a two-dimensional list of FTANodes, -1 otherwise. * @param a The list we want the index of. * @param b The two-dimensional list of FTANodes we want to search in. * @returns the index of the list. @@ -322,6 +380,9 @@ export class BDDGenerator{ } i++; } + if(i >= b.length){ + return -1; + } return i; } diff --git a/extension/src-language-server/fta/fta-diagram-generator.ts b/extension/src-language-server/fta/fta-diagram-generator.ts index 0c43acf..5837e1e 100644 --- a/extension/src-language-server/fta/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/fta-diagram-generator.ts @@ -4,9 +4,8 @@ import { SLabel, SModelElement, SModelRoot } from 'sprotty-protocol'; import { ModelFTA, isComponent, isCondition, isGate, isKNGate, isTopEvent } from '../generated/ast'; import { FTAEdge, FTANode } from './fta-interfaces'; import { FTA_EDGE_TYPE, FTA_NODE_TYPE, PARENT_TYPE } from './fta-model'; -import { getAllGateTypes, getAspect, getTargets, setLevelsForFTANodes } from './fta-utils'; import { FtaServices } from './fta-module'; -//import { determineMinimalCutSet, generateCutSets } from './bdd-generator'; +import { getAllGateTypes, getAspect, getTargets, setLevelsForFTANodes } from './fta-utils'; export class FtaDiagramGenerator extends LangiumDiagramGenerator{ @@ -44,6 +43,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ ...allGates[2]?.map(k => this.generateFTANode(k, args)).flat(1), //kn ...allGates[3]?.map(i => this.generateFTANode(i, args)).flat(1), //inhib + //after that create the edges of the gates and the top event ...allGates[0]?.map(a => this.generateAspectWithEdges(a, args)).flat(1), ...allGates[1]?.map(o => this.generateAspectWithEdges(o, args)).flat(1), @@ -61,6 +61,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ ftaNodes.push(node as FTANode); } } + const ftaEdges: FTAEdge[] = []; for(const edge of ftaChildren){ if(edge.type === FTA_EDGE_TYPE){ diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index b53092d..793f40f 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -4,6 +4,7 @@ import { LangiumSprottySharedServices } from "langium-sprotty"; import { FtaDiagramGenerator } from "./fta-diagram-generator"; + let lastUri: URI; export function addFTANotificationHandler(connection: Connection, ftaServices: FtaServices, sharedServices: LangiumSprottySharedServices): void { @@ -17,13 +18,14 @@ function addGenerateCutSetsHandler(connection: Connection, ftaServices: FtaServi const diagramGenerator = (ftaServices.diagram.DiagramGenerator) as FtaDiagramGenerator; const nodes = diagramGenerator.getNodes(); const edges = diagramGenerator.getEdges(); + const cutSets = ftaServices.bdd.Bdd.generateCutSets(nodes, edges); return cutSets; }); } -function addGenerateMinimalCutSetsHandler(connection:Connection, ftaServices: FtaServices):void{ +function addGenerateMinimalCutSetsHandler(connection: Connection, ftaServices: FtaServices):void{ connection.onRequest('generate/getMinimalCutSets', uri =>{ lastUri = uri; const diagramGenerator = (ftaServices.diagram.DiagramGenerator) as FtaDiagramGenerator; @@ -32,5 +34,5 @@ function addGenerateMinimalCutSetsHandler(connection:Connection, ftaServices: Ft const minimalCutSets = ftaServices.bdd.Bdd.determineMinimalCutSet(nodes, edges); return minimalCutSets; }); +} -} \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-model.ts b/extension/src-language-server/fta/fta-model.ts index 03fce88..dcef7f1 100644 --- a/extension/src-language-server/fta/fta-model.ts +++ b/extension/src-language-server/fta/fta-model.ts @@ -7,7 +7,6 @@ export const FTA_EDGE_TYPE = 'edge:fta'; export const BDD_NODE_TYPE = 'node:bdd'; export const BDD_EDGE_TYPE = 'edge:bdd'; - /** * The different aspects of FTA. */ diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index 16e7787..6fc697f 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -6,12 +6,12 @@ import { DefaultElementFilter, ElkFactory, ElkLayoutEngine, IElementFilter, ILay import { DiagramOptions } from 'sprotty-protocol'; import { URI } from 'vscode-uri'; import { FtaGeneratedModule, StpaGeneratedSharedModule } from '../generated/module'; +import { CutSetGenerator } from './fta-cutSet-generator'; import { FtaDiagramGenerator } from './fta-diagram-generator'; import { FtaDiagramServer } from './fta-diagramServer'; import { FtaLayoutConfigurator } from './fta-layout-config'; import { FtaSynthesisOptions } from './fta-synthesis-options'; import { FtaValidationRegistry, FtaValidator } from './fta-validator'; -import { BDDGenerator } from './bdd-generator'; @@ -36,7 +36,7 @@ export type FtaAddedServices = { FtaSynthesisOptions: FtaSynthesisOptions }, bdd: { - Bdd: BDDGenerator + Bdd: CutSetGenerator } }; @@ -69,7 +69,7 @@ export const FtaModule: Module new FtaSynthesisOptions() }, bdd:{ - Bdd: () => new BDDGenerator() + Bdd: () => new CutSetGenerator() } }; diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts index 60dae91..64dcf2c 100644 --- a/extension/src-webview/di.config.ts +++ b/extension/src-webview/di.config.ts @@ -31,8 +31,9 @@ import { sidebarModule } from './sidebar'; import { optionsModule } from './options/options-module'; import { StpaModelViewer} from './model-viewer'; import { StpaMouseListener } from './stpa-mouselistener'; -import { FTANodeView, PolylineArrowEdgeViewFTA } from './fta-views'; -import { FTANode, FTA_EDGE_TYPE, FTA_NODE_TYPE } from './fta-model'; +//import { FTANodeView, PolylineArrowEdgeViewFTA } from './fta-views'; +import { FTANode, FTA_EDGE_TYPE, FTA_NODE_TYPE} from './fta-model'; +import { FTANodeView, PolylineArrowEdgeViewFTA } from './fta-views'; const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); diff --git a/extension/src-webview/di.symbols.ts b/extension/src-webview/di.symbols.ts index d9710ab..a801967 100644 --- a/extension/src-webview/di.symbols.ts +++ b/extension/src-webview/di.symbols.ts @@ -24,4 +24,5 @@ export const DISymbol = { OptionsRenderer: Symbol("OptionsRenderer"), OptionsRegistry: Symbol("OptionsRegistry"), RenderOptionsRegistry: Symbol("RenderOptionsRegistry"), + CutSetsRegistry: Symbol("CutSetsRegistry"), }; \ No newline at end of file diff --git a/extension/src-webview/fta-model.ts b/extension/src-webview/fta-model.ts index bdf3fe3..e85777d 100644 --- a/extension/src-webview/fta-model.ts +++ b/extension/src-webview/fta-model.ts @@ -8,6 +8,7 @@ export const EDGE_TYPE = 'edge'; export const FTA_EDGE_TYPE = 'edge:fta'; + /** * Node representing a FTA component. */ @@ -24,7 +25,6 @@ export class FTANode extends SNode{ } - /** * Edge representing an edge in the relationship graph. */ diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index c4f6dec..8f0432d 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -3,18 +3,20 @@ import { inject, injectable } from 'inversify'; import { VNode } from "snabbdom"; import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SEdge, SPort, svg } from 'sprotty'; import { DISymbol } from "./di.symbols"; -import { FTAAspect, FTANode } from './fta-model'; +import { FTAAspect, FTAEdge, FTANode, FTA_EDGE_TYPE } from './fta-model'; import { ColorStyleOption, RenderOptionsRegistry } from './options/render-options-registry'; import { renderAndGate, renderCircle, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "./views-rendering"; +import { CutSetsRegistry } from './options/cut-set-registry'; /** Determines if path/aspect highlighting is currently on. */ -let highlighting: boolean; +let highlightingFTA: boolean; @injectable() export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { @inject(DISymbol.RenderOptionsRegistry) renderOptionsRegistry: RenderOptionsRegistry; + @inject(DISymbol.CutSetsRegistry) cutSetsRegistry: CutSetsRegistry; protected renderLine(edge: SEdge, segments: Point[], context: RenderingContext): VNode { const firstPoint = segments[0]; @@ -24,9 +26,12 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { path += ` L ${p.x},${p.y}`; } + highlightingFTA = this.cutSetsRegistry.getFtaHightlighting(); + // if an FTANode is selected, the components not connected to it should fade out + const hidden = edge.type == FTA_EDGE_TYPE && highlightingFTA && !(edge as FTAEdge).highlight; const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption); - return ; + return ; } /* protected renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] { @@ -46,10 +51,12 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { export class FTANodeView extends RectangularNodeView { @inject(DISymbol.RenderOptionsRegistry) renderOptionsRegistry: RenderOptionsRegistry; + @inject(DISymbol.CutSetsRegistry) cutSetsRegistry: CutSetsRegistry; render(node: FTANode, context: RenderingContext): VNode { const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption); const printNode = colorStyle == "black & white"; + const sprottyNode = colorStyle == "standard"; const coloredNode = colorStyle == "colorful"; // create the element based on the aspect of the node @@ -80,13 +87,21 @@ export class FTANodeView extends RectangularNodeView { element = renderRectangle(node); break; } + + highlightingFTA = this.cutSetsRegistry.getFtaHightlighting(); + //if an FTANode is selected, the components not connected to it should fade out + const hidden = highlightingFTA && !node.highlight; + return + class-mouseover={node.hoverFeedback} + class-hidden={hidden}> {element} {context.renderChildren(node)} ; } -} +} \ No newline at end of file diff --git a/extension/src-webview/helper-methods.ts b/extension/src-webview/helper-methods.ts index 2262ac3..3d6e06c 100644 --- a/extension/src-webview/helper-methods.ts +++ b/extension/src-webview/helper-methods.ts @@ -17,6 +17,11 @@ import { SNode, SEdge, SModelElement } from "sprotty"; import { STPAAspect, STPAEdge, STPANode, STPA_NODE_TYPE } from "./stpa-model"; +import { FTAAspect, FTAEdge, FTANode } from "./fta-model"; + + +export const allFTANodes:FTANode[] = []; +export const allFTAEdges:FTAEdge[] = []; /** * Collects all children of the nodes in {@code nodes}. @@ -178,4 +183,76 @@ export function flagSameAspect(selected: STPANode): STPANode[] { (selected.parent as STPANode).highlight = true; } return elements; -} \ No newline at end of file +} + +export function setFTANodesAndEdges(allNodes:SNode[], allEdges:SEdge[]):void{ + allNodes.forEach(node => { + if(node instanceof FTANode){ + allFTANodes.push(node); + } + }); + allEdges.forEach(edge => { + if(edge instanceof FTAEdge){ + allFTAEdges.push(edge); + } + }); +} + +export function flagHighlightedFta(highlightedCutSet: string[]):void{ + highlightCutSet(highlightedCutSet); + + let topEvent = {} as FTANode; + for(const node of allFTANodes){ + if(node.level === 0){ + topEvent = node; + } + } + + highlightPath(topEvent); + // highlightEdgesInPath(topEvent); + + +} +function highlightCutSet(highlightCutSet: string[]):void{ + for(const node of allFTANodes){ + if(highlightCutSet.includes(node.id)){ + node.highlight = true; + + }else{ + node.highlight = false; + /* for(const edge of node.outgoingEdges){ + (edge as FTAEdge).highlight = false; + } */ + } + } +} + + +function highlightPath(start:FTANode):boolean{ + + if(start.aspect === (FTAAspect.CONDITION || FTAAspect.COMPONENT)){ + if(start.highlight === true){ + return true; + }else{ + return false; + } + }else{ + //get all children of current node. + for(const edge of start.outgoingEdges){ + const child = edge.target as SNode; + const ftaNode = child as FTANode; + if(highlightPath(ftaNode) === true){ + start.highlight = true; + (edge as FTAEdge).highlight = true; + }else{ + (edge as FTAEdge).highlight = false; + } + } + + if(start.highlight === true){ + return true; + } + } + + return false; +} diff --git a/extension/src-webview/options/actions.ts b/extension/src-webview/options/actions.ts index 424c316..27ded11 100644 --- a/extension/src-webview/options/actions.ts +++ b/extension/src-webview/options/actions.ts @@ -127,4 +127,45 @@ export namespace SendConfigAction { export function isThisAction(action: Action): action is SendConfigAction { return action.kind === SendConfigAction.KIND; } -} \ No newline at end of file +} + + +export interface SendCutSetAction extends Action { + kind: typeof SendCutSetAction.KIND; + cutSets: { id: string, value: any; }[]; +} + + export namespace SendCutSetAction { + export const KIND = "sendCutSet"; + + export function create(cutSets: { id: string, value: any; }[]): SendCutSetAction { + return { + kind: KIND, + cutSets + }; + } + + export function isThisAction(action: Action): action is SendCutSetAction { + return action.kind === SendCutSetAction.KIND; + } +} + +export interface SelectCutSetAction extends Action { + kind: typeof SelectCutSetAction.KIND; + id: string; +} + + export namespace SelectCutSetAction { + export const KIND = "selectCutSet"; + + export function create(id: string): SelectCutSetAction { + return { + kind: KIND, + id + }; + } + + export function isThisAction(action: Action): action is SelectCutSetAction { + return action.kind === SelectCutSetAction.KIND; + } +} \ No newline at end of file diff --git a/extension/src-webview/options/cut-set-panel.tsx b/extension/src-webview/options/cut-set-panel.tsx new file mode 100644 index 0000000..704799f --- /dev/null +++ b/extension/src-webview/options/cut-set-panel.tsx @@ -0,0 +1,48 @@ +/** @jsx html */ +import { SidebarPanel } from "../sidebar"; +import { html } from "sprotty"; +import { DISymbol } from "../di.symbols"; +import { inject, injectable, postConstruct } from "inversify"; +import { VNode } from "snabbdom"; +import { FeatherIcon } from '../feather-icons-snabbdom/feather-icons-snabbdom'; +import { CutSetsRegistry } from "./cut-set-registry"; +import { OptionsRenderer } from "./options-renderer"; + +@injectable() +export class CutSetPanel extends SidebarPanel{ + + + @inject(DISymbol.CutSetsRegistry) private cutSetsRegistry: CutSetsRegistry; + @inject(DISymbol.OptionsRenderer) private optionsRenderer: OptionsRenderer; + + + + @postConstruct() + init():void{ + this.cutSetsRegistry.onChange(() => this.update()); + } + get id(): string { + return "cut-set-panel"; + } + + get title(): string { + return "Cut sets"; + } + + render(): VNode { + return ( +
+
+
Cut sets
+ {this.optionsRenderer.renderRenderOptions( + this.cutSetsRegistry.allOptions + )} +
+
+ ); + } + + get icon(): VNode { + return ; + } +} \ No newline at end of file diff --git a/extension/src-webview/options/cut-set-registry.ts b/extension/src-webview/options/cut-set-registry.ts new file mode 100644 index 0000000..bf4e577 --- /dev/null +++ b/extension/src-webview/options/cut-set-registry.ts @@ -0,0 +1,83 @@ +import { inject, injectable, postConstruct } from "inversify"; +import { Action, UpdateModelAction } from "sprotty-protocol"; +import { Registry } from "../base/registry"; +import { VsCodeApi } from "sprotty-vscode-webview/lib/services"; +import { ICommand } from "sprotty"; +import { RenderOption, TransformationOptionType } from "./option-models"; +import { SelectCutSetAction, SendCutSetAction } from "./actions"; +import { flagHighlightedFta } from "../helper-methods"; + + +export class DropDownMenuOption implements RenderOption{ + static readonly ID: string = 'cut-sets'; + static readonly NAME: string = 'Cut Sets'; + readonly id: string = DropDownMenuOption.ID; + readonly currentId:string = DropDownMenuOption.ID; + readonly name: string = DropDownMenuOption.NAME; + readonly type: TransformationOptionType = TransformationOptionType.DROPDOWN; + availableValues: { displayName: string; id: string }[] = [{displayName: "---", id: "---"}]; + readonly initialValue: { displayName: string; id: string } = {displayName: "---", id: "---"}; + currentValue = {displayName: "---", id: "---"}; +} + + +export interface RenderOptionType { + readonly ID: string, + readonly NAME: string, + new(): RenderOption, +} + + +/** {@link Registry} that stores and updates different render options. */ +@injectable() +export class CutSetsRegistry extends Registry{ + + private _options: Map = new Map(); + private selectedCutSet:string = ""; + private ftaHightlighting = false; + + @inject(VsCodeApi) private vscodeApi: VsCodeApi; + + constructor(){ + super(); + } + + + + handle(action: Action): void | Action | ICommand{ + if(SendCutSetAction.isThisAction(action)){ + //this.vscodeApi.postMessage(action.cutSets); + const dropDownOption = new DropDownMenuOption(); + for(const entry of action.cutSets){ + dropDownOption.availableValues.push({displayName: entry.id , id: entry.id}); + } + + this._options.set('cut-sets', dropDownOption); + this.notifyListeners(); + }else if(SelectCutSetAction.isThisAction(action)){ + this.selectedCutSet = action.id; + this.ftaHightlighting = true; + this.highlightSelectedNodes(this.selectedCutSet); + + } + return UpdateModelAction.create([], { animate: false, cause: action }); + } + + get allOptions():RenderOption[]{ + return Array.from(this._options.values()); + } + + + highlightSelectedNodes(selectedCutSet:string):void{ + const selectedSet = selectedCutSet.slice(1,-1); // remove the brackets [] + if(selectedSet === '-'){ + this.ftaHightlighting = false; + } + + const componentsToHighlight = selectedSet.split(","); + flagHighlightedFta(componentsToHighlight); + } + getFtaHightlighting():boolean{ + return this.ftaHightlighting; + } +} \ No newline at end of file diff --git a/extension/src-webview/options/options-module.ts b/extension/src-webview/options/options-module.ts index 2589207..431240b 100644 --- a/extension/src-webview/options/options-module.ts +++ b/extension/src-webview/options/options-module.ts @@ -18,12 +18,14 @@ import { ContainerModule } from "inversify"; import { configureActionHandler, TYPES } from "sprotty"; import { DISymbol } from "../di.symbols"; -import { SetRenderOptionAction, ResetRenderOptionsAction, SendConfigAction } from "./actions"; +import { SetRenderOptionAction, ResetRenderOptionsAction, SendConfigAction, SendCutSetAction, SelectCutSetAction } from "./actions"; import { OptionsRenderer } from "./options-renderer"; import { GeneralPanel } from "./general-panel"; import { RenderOptionsRegistry } from "./render-options-registry"; import { OptionsRegistry } from "./options-registry"; import { OptionsPanel } from "./options-panel"; +import { CutSetPanel } from "./cut-set-panel"; +import { CutSetsRegistry } from "./cut-set-registry"; // import { VsCodeApi } from "sprotty-vscode-webview/lib/services"; /** Module that configures option related panels and registries. */ @@ -34,6 +36,9 @@ export const optionsModule = new ContainerModule((bind, _, isBound) => { bind(OptionsPanel).toSelf().inSingletonScope(); bind(DISymbol.SidebarPanel).toService(OptionsPanel); + bind(CutSetPanel).toSelf().inSingletonScope(); + bind(DISymbol.SidebarPanel).toService(CutSetPanel); + // bind(VsCodeApi); bind(DISymbol.OptionsRenderer).to(OptionsRenderer); @@ -41,9 +46,13 @@ export const optionsModule = new ContainerModule((bind, _, isBound) => { bind(TYPES.IActionHandlerInitializer).toService(DISymbol.OptionsRegistry); bind(DISymbol.RenderOptionsRegistry).to(RenderOptionsRegistry).inSingletonScope(); + + bind(DISymbol.CutSetsRegistry).to(CutSetsRegistry).inSingletonScope(); const ctx = { bind, isBound }; configureActionHandler(ctx, SetRenderOptionAction.KIND, DISymbol.RenderOptionsRegistry); configureActionHandler(ctx, ResetRenderOptionsAction.KIND, DISymbol.RenderOptionsRegistry); configureActionHandler(ctx, SendConfigAction.KIND, DISymbol.RenderOptionsRegistry); + configureActionHandler(ctx, SendCutSetAction.KIND, DISymbol.CutSetsRegistry); + configureActionHandler(ctx, SelectCutSetAction.KIND, DISymbol.CutSetsRegistry); }); diff --git a/extension/src-webview/options/options-renderer.tsx b/extension/src-webview/options/options-renderer.tsx index 28b8639..6788f0c 100644 --- a/extension/src-webview/options/options-renderer.tsx +++ b/extension/src-webview/options/options-renderer.tsx @@ -193,7 +193,20 @@ export class OptionsRenderer { onChange={this.handleRenderOptionChange.bind(this, option)} availableValues = {(option as ChoiceRenderOption).availableValues} /> - ) + ); + case TransformationOptionType.DROPDOWN: + return ( + + ); default: console.error("Unsupported option type for option:", option.name); return ""; diff --git a/extension/src-webview/options/render-options-registry.ts b/extension/src-webview/options/render-options-registry.ts index 9a2d2af..1cfbc8e 100644 --- a/extension/src-webview/options/render-options-registry.ts +++ b/extension/src-webview/options/render-options-registry.ts @@ -19,7 +19,7 @@ import { inject, injectable, postConstruct } from "inversify"; import { ICommand } from "sprotty"; import { Action, UpdateModelAction } from "sprotty-protocol"; import { Registry } from "../base/registry"; -import { ResetRenderOptionsAction, SendConfigAction, SetRenderOptionAction } from "./actions"; +import { ResetRenderOptionsAction, SelectCutSetAction, SendConfigAction, SendCutSetAction, SetRenderOptionAction } from "./actions"; import { ChoiceRenderOption, RenderOption, TransformationOptionType } from "./option-models"; import { VsCodeApi } from "sprotty-vscode-webview/lib/services"; @@ -117,8 +117,14 @@ export class RenderOptionsRegistry extends Registry { if (SetRenderOptionAction.isThisAction(action)) { const option = this._renderOptions.get(action.id); + if(action.id === 'cut-sets'){ + const selectCutSetAction = {kind: SelectCutSetAction.KIND, id: action.value}; + this.vscodeApi.postMessage({action: selectCutSetAction}); + this.notifyListeners(); + return; + } + if (!option) {return;} - option.currentValue = action.value; const sendAction = { kind: SendConfigAction.KIND, options: [{ id: action.id, value: action.value }] }; this.vscodeApi.postMessage({ action: sendAction }); @@ -137,7 +143,7 @@ export class RenderOptionsRegistry extends Registry { option.currentValue = element.value; }); this.notifyListeners(); - } + } return UpdateModelAction.create([], { animate: false, cause: action }); } diff --git a/extension/src-webview/views.tsx b/extension/src-webview/views.tsx index fbe9f06..b202514 100644 --- a/extension/src-webview/views.tsx +++ b/extension/src-webview/views.tsx @@ -16,19 +16,19 @@ */ /** @jsx svg */ +import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; -import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SEdge, SNode, svg, SPort, toDegrees, SGraphView, SGraph } from 'sprotty'; -import { injectable } from 'inversify'; -import { STPANode, PARENT_TYPE, STPA_NODE_TYPE, CS_EDGE_TYPE, STPAAspect, STPAEdge, STPA_EDGE_TYPE, CS_NODE_TYPE } from './stpa-model'; -import { renderCircle, renderDiamond, renderHexagon, renderMirroredTriangle, renderPentagon, renderRectangle, renderRoundedRectangle, renderTrapez, renderTriangle } from './views-rendering'; -import { inject } from 'inversify'; -import { collectAllChildren } from './helper-methods'; +import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SEdge, SGraph, SGraphView, SNode, SPort, svg, toDegrees } from 'sprotty'; import { DISymbol } from './di.symbols'; +import { collectAllChildren, setFTANodesAndEdges } from './helper-methods'; import { ColorStyleOption, DifferentFormsOption, RenderOptionsRegistry, ShowCSOption, ShowRelationshipGraphOption } from './options/render-options-registry'; +import { CS_EDGE_TYPE, CS_NODE_TYPE, PARENT_TYPE, STPAAspect, STPAEdge, STPANode, STPA_EDGE_TYPE, STPA_NODE_TYPE } from './stpa-model'; +import { renderCircle, renderDiamond, renderHexagon, renderMirroredTriangle, renderPentagon, renderRectangle, renderRoundedRectangle, renderTrapez, renderTriangle } from './views-rendering'; /** Determines if path/aspect highlighting is currently on. */ let highlighting: boolean; + @injectable() export class PolylineArrowEdgeView extends PolylineEdgeView { @@ -199,8 +199,11 @@ export class STPAGraphView extends SGraphView { highlighting = allNodes.find(node => { return node instanceof STPANode && node.highlight }) !== undefined; + + setFTANodesAndEdges(allNodes, model.children as SEdge[]); return super.render(model, context, args); } -} \ No newline at end of file +} + diff --git a/extension/src/actions.ts b/extension/src/actions.ts index 3c18a1b..5330c55 100644 --- a/extension/src/actions.ts +++ b/extension/src/actions.ts @@ -57,4 +57,46 @@ export namespace SendConfigAction { export function isThisAction(action: Action): action is SendConfigAction { return action.kind === SendConfigAction.KIND; } +} + + + +export interface SendCutSetAction extends Action { + kind: typeof SendCutSetAction.KIND; + cutSets: { id: string, value: any; }[]; +} + + export namespace SendCutSetAction { + export const KIND = "sendCutSet"; + + export function create(cutSets: { id: string, value: any; }[]): SendCutSetAction { + return { + kind: KIND, + cutSets + }; + } + + export function isThisAction(action: Action): action is SendCutSetAction { + return action.kind === SendCutSetAction.KIND; + } +} + +export interface SelectCutSetAction extends Action { + kind: typeof SelectCutSetAction.KIND; + id: string; +} + + export namespace SelectCutSetAction { + export const KIND = "selectCutSet"; + + export function create(id: string): SelectCutSetAction { + return { + kind: KIND, + id + }; + } + + export function isThisAction(action: Action): action is SelectCutSetAction { + return action.kind === SelectCutSetAction.KIND; + } } \ No newline at end of file diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index 99cfbaa..df713c7 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -22,11 +22,11 @@ import { LspLabelEditActionHandler, SprottyLspEditVscodeExtension, WorkspaceEdit import { SprottyWebview } from 'sprotty-vscode/lib/sprotty-webview'; import * as vscode from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'; -import { UpdateViewAction } from './actions'; +import { FTANode } from '../src-language-server/fta/fta-interfaces'; +import { SendCutSetAction, UpdateViewAction } from './actions'; import { ContextTablePanel } from './context-table-panel'; import { StpaFormattingEditProvider } from './stpa-formatter'; import { StpaLspWebview } from './wview'; -import { FTANode } from '../src-language-server/fta/fta-interfaces'; export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { @@ -42,6 +42,8 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { /** needed for undo/redo actions when ID enforcement is active*/ protected ignoreNextTextChange: boolean = false; + protected panel: vscode.WebviewPanel | null = null; + constructor(context: vscode.ExtensionContext) { super('pasta', context); // user changed configuration settings @@ -177,12 +179,13 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { this.context.subscriptions.push( vscode.commands.registerCommand(this.extensionPrefix + '.generate.cutSets', async (...commandArgs: any[]) =>{ this.lastUri = (commandArgs[0] as vscode.Uri).toString(); - const cutSets:FTANode[][] = await this.languageClient.sendRequest('generate/getCutSets', this.lastUri); - - const outputCutSets = vscode.window.createOutputChannel("All cut sets"); - outputCutSets.append("Cut sets: " + "\n" + this.toString(cutSets)); - outputCutSets.show(); + const cutSets:FTANode[][] = await this.languageClient.sendRequest('generate/getCutSets', this.lastUri); + + this.dispatchCutSetsToWebview(cutSets); + const outputCutSets = vscode.window.createOutputChannel("All cut sets"); + outputCutSets.append("The resulting " + cutSets.length + " cut sets are: \n" + this.CutSetToString(cutSets)); + outputCutSets.show(); }) ); this.context.subscriptions.push( @@ -190,32 +193,51 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { this.lastUri = (commandArgs[0] as vscode.Uri).toString(); const minimalCutSets:FTANode[][] = await this.languageClient.sendRequest('generate/getMinimalCutSets', this.lastUri); + this.dispatchCutSetsToWebview(minimalCutSets); + const outputMinimalCutSets = vscode.window.createOutputChannel("All minimal cut sets"); - outputMinimalCutSets.append("Minimal cut sets: " + "\n" + this.toString(minimalCutSets)); - outputMinimalCutSets.show(); + outputMinimalCutSets.append("The resulting " + minimalCutSets.length + " minimal cut sets are: \n" + this.CutSetToString(minimalCutSets)); + outputMinimalCutSets.show(); }) - ); - - + ); + } + protected dispatchCutSetsToWebview(cutSets:FTANode[][]):void{ + const cutSetDropDownList: { id: string, value: any; }[] = []; + for(const set of cutSets){ + let id = "["; + for(const element of set){ + if(set.indexOf(element) === set.length -1){ + id += element.id; + }else{ + id = id + element.id + ","; + } + } + id += "]"; + cutSetDropDownList.push({id: id, value: set}); + } + this.singleton?.dispatch({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); } - protected toString(cutSets:FTANode[][]):string{ - let result = "[" ; + protected CutSetToString(cutSets:FTANode[][]):string{ + let result = "["; + for(const set of cutSets){ - result = result + "["; + result += "["; for(const element of set){ - result = result + element.id; - if(set.indexOf(element) === set.length - 1){ - break; + if(set.indexOf(element) === set.length -1){ + result += element.id; + }else{ + result = result + element.id + ","; } - result = result + ","; } - result = result + "]"; - if(cutSets.indexOf(set) !== cutSets.length -1){ - result = result + ","; - } + result += "]"; + if(cutSets.indexOf(set) === cutSets.length -1){ + result += "] \n"; + }else{ + result += ", \n"; + } } - result = result + "]"; + return result; } @@ -291,8 +313,9 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { }); webview.addActionHandler(WorkspaceEditActionHandler); webview.addActionHandler(LspLabelEditActionHandler); - this.singleton = webview; + + return webview; } diff --git a/extension/src/wview.ts b/extension/src/wview.ts index 2adfbc2..24157c1 100644 --- a/extension/src/wview.ts +++ b/extension/src/wview.ts @@ -18,10 +18,12 @@ import { hasOwnProperty, isActionMessage, SelectAction } from 'sprotty-protocol'; import { SprottyLspWebview } from "sprotty-vscode/lib/lsp"; import * as vscode from 'vscode'; -import { SendConfigAction } from './actions'; +import { SelectCutSetAction, SendConfigAction } from './actions'; + export class StpaLspWebview extends SprottyLspWebview { + protected receiveFromWebview(message: any): Promise { // TODO: for multiple language support here the current language muste be determined if (isRenderOptionsRegistryReadyMessage(message)) { @@ -34,8 +36,10 @@ export class StpaLspWebview extends SprottyLspWebview { case SelectAction.KIND: this.handleSelectAction(message.action as SelectAction); break; + case SelectCutSetAction.KIND: + this.selectCutSet(message.action as SelectCutSetAction); + break; } - } return super.receiveFromWebview(message); } @@ -71,6 +75,11 @@ export class StpaLspWebview extends SprottyLspWebview { const configOptions = vscode.workspace.getConfiguration('pasta'); action.options.forEach(element => configOptions.update(element.id, element.value)); } + + protected selectCutSet(action: SelectCutSetAction):void{ + this.dispatch(action); + } + } interface RenderOptionsRegistryReadyMessage { @@ -80,3 +89,5 @@ interface RenderOptionsRegistryReadyMessage { function isRenderOptionsRegistryReadyMessage(object: unknown): object is RenderOptionsRegistryReadyMessage { return hasOwnProperty(object, 'optionRegistryReadyMessage'); } + + diff --git a/test.fta b/test.fta index b584dc7..065be12 100644 --- a/test.fta +++ b/test.fta @@ -12,7 +12,7 @@ Conditions U "In Use" TopEvent -"System Failure" = G1 +"System Failure" = G1 Gates G1 = U inhibits G2 @@ -21,3 +21,19 @@ G3 = G4 and G5 G4 = C1 or PS or G6 G5 = C2 or PS or G6 G6 = 2 of M1, M2, M3 + + +/* Gates +G1 = 3 of M1, G2, G3, C2 +G2 = U inhibits M2 +G3 = M3 and G4 +G4 = B or PS or C1 */ + + +/* Gates +G1 = G2 or B +G2 = G3 and G4 +G3 = C1 or G5 +G4 = C2 or G6 +G5 = 2 of M1, M2, M3 +G6 = 2 of S, T, K */ \ No newline at end of file diff --git a/teststpa.stpa b/teststpa.stpa index 13a29e8..52e977f 100644 --- a/teststpa.stpa +++ b/teststpa.stpa @@ -1,4 +1,84 @@ Losses -L1 "test" -L2 "FSDFA" -L3 "asd" +L1 "Loss of life or serious injury to people" +L2 "Damage to the aircraft or objects outside the aircraft" + +Hazards +H1 "Loss of aircraft control" [L1, L2] +H2 "Aircraft comes too close to other objects" [L1, L2] { + H2.1 "Deceleration is insufficient" + "Acceleration" + H2.2 "Asymmetric acceleration maneuvers aircraft toward other objects" + H2.3 "Excessive acceleration provided while taxiing" +} + +SystemConstraints +SC2 "Aircraft must have safe distance to other objects" [H2] { + SC2.1 "Deceleration must occur within TBD seconds of landing or rejected takeoff at a rate of at least TBD m/s2" [H2.1] + SC2.2 "Assymetric acceleration must not ..." [H2.2] +} + +ControlStructure +Aircraft { + FlightCrew { + hierarchyLevel 0 + processModel { + BCSUmode: [on, off] + } + controlActions { + [mc "Manual Controls" ]-> OtherSubsystems + [powerOff "Power Off BSCU", powerOn "Power On BSCU"] -> BSCU + [manual "Manual Braking"] -> Wheels + } + } + OtherSubsystems { + hierarchyLevel 1 + feedback { + [modes "Other system modes", states "states"] -> FlightCrew + } + } + BSCU { + hierarchyLevel 1 + controlActions { + [brake "Brake"] -> Wheels + } + feedback { + [mode "BSCU mode", faults "BSCU faults"] -> FlightCrew + } + } + Wheels { + hierarchyLevel 2 + feedback { + [speed "Wheel speed"] -> BSCU + } + } +} + +Responsibilities +BSCU { + R1 "Actuate brakes when requested" [SC2.1] + R2 "Pulse brakes in case of a skid" [SC2.2] +} +FlightCrew { + R3 "Manually brake in case of a malfunction" [SC2.1, SC2.2] +} + +UCAs +FlightCrew.powerOff { + notProviding { + UCA1 "Crew does not provide BSCU Power Off when abnormal WBS behavior occurs" [H2.1] + } + providing { + UCA2 "Crew provides BSCU Power Off when Anti-Skid functionality is needed and WBS is functioning normally" [H2.3] + } + tooEarly/Late {} + stoppedTooSoon {} +} + +ControllerConstraints +C1 "Crew must provide the BSCU Power Off control action during abnormal WBS behavior" [UCA1] +C2 "Crew must not provide the BSCU Power Off control action when Anti-Skid functionality is needed" [UCA2] + +LossScenarios +Scenario1 for UCA1 "Abnormal WBS behavior occurs. Crew does not power off ..." [H2.1] +Scenario2 "Insufficient braking is applied due to ..." [H2.1] + From d2d9bc6c9436b9346cea9bc4331dee4382d502ab Mon Sep 17 00:00:00 2001 From: Orhan Date: Tue, 4 Jul 2023 12:56:43 +0200 Subject: [PATCH 18/61] Added some comments --- .../fta/fta-cutSet-generator.ts | 19 ++++++++++++++----- .../fta/fta-diagram-generator.ts | 8 ++++++++ .../fta/fta-message-handler.ts | 15 +++++++++++++++ .../src-language-server/fta/fta-utils.ts | 2 +- extension/src-webview/fta-views.tsx | 12 +----------- extension/src-webview/helper-methods.ts | 6 +----- .../src-webview/options/cut-set-registry.ts | 5 ++--- .../options/render-options-registry.ts | 2 +- extension/src/language-extension.ts | 11 +++++++++-- 9 files changed, 52 insertions(+), 28 deletions(-) diff --git a/extension/src-language-server/fta/fta-cutSet-generator.ts b/extension/src-language-server/fta/fta-cutSet-generator.ts index 6e2663a..975a703 100644 --- a/extension/src-language-server/fta/fta-cutSet-generator.ts +++ b/extension/src-language-server/fta/fta-cutSet-generator.ts @@ -4,7 +4,12 @@ import { FTAAspect } from "./fta-model"; export class CutSetGenerator{ - + /** + * Takes the Fault Tree and returns a two-dimensional array of FTANodes where every inner list resembles a minimal cut set. + * @param allNodes All Nodes in the graph. + * @param allEdges All Edges in the graph. + * @returns A list of lists that that contains every minimal cut set of the given Fault Tree. + */ determineMinimalCutSet(allNodes:FTANode[], allEdges:FTAEdge[]):FTANode[][]{ const bdd = this.generateCutSets(allNodes, allEdges); @@ -312,11 +317,15 @@ export class CutSetGenerator{ * @returns the child of the topevent. */ getChildOfTopEvent(allNodes:FTANode[], allEdges:FTAEdge[]): FTANode{ + let topEvent:FTANode = {} as FTANode; for(const node of allNodes){ - for(const edge of allEdges){ - if(node.level === 0 && edge.sourceId === node.id){ - return this.getNodeWithID(allNodes, edge.targetId); - } + if(node.aspect === FTAAspect.TOPEVENT){ + topEvent = node; + } + } + for(const edge of allEdges){ + if(edge.sourceId === topEvent.id){ + return this.getNodeWithID(allNodes, edge.targetId); } } diff --git a/extension/src-language-server/fta/fta-diagram-generator.ts b/extension/src-language-server/fta/fta-diagram-generator.ts index 5837e1e..b2a6ab8 100644 --- a/extension/src-language-server/fta/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/fta-diagram-generator.ts @@ -89,9 +89,17 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ } + /** + * Getter method for every FTANode in the Fault Tree. + * @returns Every FTANode in the Fault Tree. + */ public getNodes():FTANode[]{ return this.allNodes; } + /** + * Getter method for every FTAEdge in the Fault Tree. + * @returns Every FTAEdge in the Fault Tree. + */ public getEdges():FTAEdge[]{ return this.allEdges; } diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index 793f40f..47c4c39 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -7,11 +7,21 @@ import { FtaDiagramGenerator } from "./fta-diagram-generator"; let lastUri: URI; +/** + * Adds handlers for notifications regarding fta. + * @param connection + * @param stpaServices + */ export function addFTANotificationHandler(connection: Connection, ftaServices: FtaServices, sharedServices: LangiumSprottySharedServices): void { addGenerateCutSetsHandler(connection, ftaServices); addGenerateMinimalCutSetsHandler(connection, ftaServices); } +/** + * Adds handlers for requests regarding the cut sets. + * @param connection + * @param ftaServices + */ function addGenerateCutSetsHandler(connection: Connection, ftaServices: FtaServices):void{ connection.onRequest('generate/getCutSets', uri =>{ lastUri = uri; @@ -25,6 +35,11 @@ function addGenerateCutSetsHandler(connection: Connection, ftaServices: FtaServi } +/** + * Adds handlers for requests regarding the minimal cut sets. + * @param connection + * @param ftaServices + */ function addGenerateMinimalCutSetsHandler(connection: Connection, ftaServices: FtaServices):void{ connection.onRequest('generate/getMinimalCutSets', uri =>{ lastUri = uri; diff --git a/extension/src-language-server/fta/fta-utils.ts b/extension/src-language-server/fta/fta-utils.ts index dc25373..fee972e 100644 --- a/extension/src-language-server/fta/fta-utils.ts +++ b/extension/src-language-server/fta/fta-utils.ts @@ -115,7 +115,7 @@ function getTopEvent(nodes: FTANode[]): FTANode{ /** * Recursively determine the level of all nodes, starting with the top event. * @param nodes All the nodes on the current layer we want to look at. At the start, this is just the top event. - * @param layer The current layer we want to assign. + * @param level The current level we want to assign. * @param edges All edges in the graph. * @param allNodes All nodes in the graph. */ diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index 8f0432d..4df2d12 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -33,17 +33,7 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption); return ; } - /* - protected renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] { - const p1 = segments[segments.length - 2]; - const p2 = segments[segments.length - 1]; - - const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption); - return [ - - ]; - } - */ + } diff --git a/extension/src-webview/helper-methods.ts b/extension/src-webview/helper-methods.ts index 3d6e06c..4b82135 100644 --- a/extension/src-webview/helper-methods.ts +++ b/extension/src-webview/helper-methods.ts @@ -203,13 +203,12 @@ export function flagHighlightedFta(highlightedCutSet: string[]):void{ let topEvent = {} as FTANode; for(const node of allFTANodes){ - if(node.level === 0){ + if(node.aspect === FTAAspect.TOPEVENT){ topEvent = node; } } highlightPath(topEvent); - // highlightEdgesInPath(topEvent); } @@ -220,9 +219,6 @@ function highlightCutSet(highlightCutSet: string[]):void{ }else{ node.highlight = false; - /* for(const edge of node.outgoingEdges){ - (edge as FTAEdge).highlight = false; - } */ } } } diff --git a/extension/src-webview/options/cut-set-registry.ts b/extension/src-webview/options/cut-set-registry.ts index bf4e577..922d11e 100644 --- a/extension/src-webview/options/cut-set-registry.ts +++ b/extension/src-webview/options/cut-set-registry.ts @@ -1,4 +1,4 @@ -import { inject, injectable, postConstruct } from "inversify"; +import { inject, injectable } from "inversify"; import { Action, UpdateModelAction } from "sprotty-protocol"; import { Registry } from "../base/registry"; import { VsCodeApi } from "sprotty-vscode-webview/lib/services"; @@ -46,7 +46,6 @@ export class CutSetsRegistry extends Registry{ handle(action: Action): void | Action | ICommand{ if(SendCutSetAction.isThisAction(action)){ - //this.vscodeApi.postMessage(action.cutSets); const dropDownOption = new DropDownMenuOption(); for(const entry of action.cutSets){ dropDownOption.availableValues.push({displayName: entry.id , id: entry.id}); @@ -67,7 +66,6 @@ export class CutSetsRegistry extends Registry{ return Array.from(this._options.values()); } - highlightSelectedNodes(selectedCutSet:string):void{ const selectedSet = selectedCutSet.slice(1,-1); // remove the brackets [] if(selectedSet === '-'){ @@ -77,6 +75,7 @@ export class CutSetsRegistry extends Registry{ const componentsToHighlight = selectedSet.split(","); flagHighlightedFta(componentsToHighlight); } + getFtaHightlighting():boolean{ return this.ftaHightlighting; } diff --git a/extension/src-webview/options/render-options-registry.ts b/extension/src-webview/options/render-options-registry.ts index 1cfbc8e..044a8f3 100644 --- a/extension/src-webview/options/render-options-registry.ts +++ b/extension/src-webview/options/render-options-registry.ts @@ -19,7 +19,7 @@ import { inject, injectable, postConstruct } from "inversify"; import { ICommand } from "sprotty"; import { Action, UpdateModelAction } from "sprotty-protocol"; import { Registry } from "../base/registry"; -import { ResetRenderOptionsAction, SelectCutSetAction, SendConfigAction, SendCutSetAction, SetRenderOptionAction } from "./actions"; +import { ResetRenderOptionsAction, SelectCutSetAction, SendConfigAction, SetRenderOptionAction } from "./actions"; import { ChoiceRenderOption, RenderOption, TransformationOptionType } from "./option-models"; import { VsCodeApi } from "sprotty-vscode-webview/lib/services"; diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index df713c7..c09c6ff 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -42,8 +42,6 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { /** needed for undo/redo actions when ID enforcement is active*/ protected ignoreNextTextChange: boolean = false; - protected panel: vscode.WebviewPanel | null = null; - constructor(context: vscode.ExtensionContext) { super('pasta', context); // user changed configuration settings @@ -201,6 +199,10 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { }) ); } + /** + * Sends the cut sets to webview as a SendCutSetAction so that they can be displayed in a dropdown menu. + * @param cutSets The (minimal) cut sets of the current Fault Tree. + */ protected dispatchCutSetsToWebview(cutSets:FTANode[][]):void{ const cutSetDropDownList: { id: string, value: any; }[] = []; for(const set of cutSets){ @@ -218,6 +220,11 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { this.singleton?.dispatch({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); } + /** + * Takes all (minimal) cut sets and returns a string that resembles it, so it can be displayed in the console. + * @param cutSets The (minimal) cut sets of the current Fault Tree. + * @returns A string that resembles the cut sets. + */ protected CutSetToString(cutSets:FTANode[][]):string{ let result = "["; From 72bbe7071306050d4edfbbfed88862c77b4d156a Mon Sep 17 00:00:00 2001 From: Orhan Date: Wed, 12 Jul 2023 12:27:49 +0200 Subject: [PATCH 19/61] Fixes --- extension/package.json | 136 +++--- extension/src-language-server/fta.langium | 12 +- .../fta/fta-cutSet-generator.ts | 430 +++++++++++++----- .../fta/fta-diagram-generator.ts | 223 ++++----- .../fta/fta-diagramServer.ts | 99 ---- .../src-language-server/fta/fta-interfaces.ts | 38 +- .../fta/fta-layout-config.ts | 33 +- .../fta/fta-message-handler.ts | 69 +-- .../src-language-server/fta/fta-model.ts | 27 +- .../src-language-server/fta/fta-module.ts | 65 +-- .../fta/fta-synthesis-options.ts | 15 - .../src-language-server/fta/fta-utils.ts | 202 ++++---- .../src-language-server/fta/fta-validator.ts | 36 +- extension/src-language-server/handler.ts | 1 - extension/src-language-server/main.ts | 2 +- .../src-language-server/options/actions.ts | 6 +- .../stpa/stpa-diagramServer.ts | 18 +- .../src-language-server/stpa/stpa-module.ts | 2 +- extension/src-language-server/utils.ts | 10 +- extension/src-webview/css/fta-diagram.css | 245 +++------- extension/src-webview/css/fta-theme.css | 2 +- extension/src-webview/di.config.ts | 8 +- extension/src-webview/fta-model.ts | 47 +- extension/src-webview/fta-views.tsx | 133 ++++-- extension/src-webview/helper-methods.ts | 71 --- extension/src-webview/model-viewer.ts | 1 - extension/src-webview/options/actions.ts | 22 +- .../src-webview/options/cut-set-panel.tsx | 17 + .../src-webview/options/cut-set-registry.ts | 90 ++-- .../src-webview/options/options-module.ts | 13 +- .../src-webview/options/options-renderer.tsx | 18 +- .../options/render-options-registry.ts | 11 +- extension/src-webview/views.tsx | 4 +- extension/src/actions.ts | 24 +- extension/src/language-extension.ts | 63 +-- extension/src/wview.ts | 10 +- test.fta | 39 -- teststpa.stpa | 84 ---- 38 files changed, 1027 insertions(+), 1299 deletions(-) delete mode 100644 extension/src-language-server/fta/fta-diagramServer.ts delete mode 100644 extension/src-language-server/fta/fta-synthesis-options.ts delete mode 100644 test.fta delete mode 100644 teststpa.stpa diff --git a/extension/package.json b/extension/package.json index 9721001..a613418 100644 --- a/extension/package.json +++ b/extension/package.json @@ -113,82 +113,62 @@ "category": "Context Table" }, { - "command": "stpa.diagram.fit", + "command": "pasta.diagram.fit", "title": "Fit to Screen", "category": "STPA Diagram" }, { - "command": "stpa.diagram.center", + "command": "pasta.diagram.center", "title": "Center selection", "category": "STPA Diagram" }, { - "command": "stpa.diagram.delete", + "command": "pasta.diagram.delete", "title": "Delete selected element", "category": "STPA Diagram" }, { - "command": "stpa.diagram.export", + "command": "pasta.diagram.export", "title": "Export diagram to SVG", "category": "STPA Diagram" }, { - "command": "fta.diagram.fit", - "title": "Fit to Screen", - "category": "FTA Diagram" - }, - { - "command": "fta.diagram.center", - "title": "Center selection", - "category": "FTA Diagram" - }, - { - "command": "fta.diagram.delete", - "title": "Delete selected element", - "category": "FTA Diagram" - }, - { - "command": "fta.diagram.export", - "title": "Export diagram to SVG", - "category": "FTA Diagram" - }, - { - "command": "stpa.checks.setCheckResponsibilitiesForConstraints", + "command": "pasta.checks.setCheckResponsibilitiesForConstraints", "title": "Set the responsibilities for constraints check", "category": "STPA Checks" }, { - "command": "stpa.checks.checkConstraintsForUCAs", + "command": "pasta.checks.checkConstraintsForUCAs", "title": "Set the constraints for UCA check", "category": "STPA Checks" }, { - "command": "stpa.checks.checkScenariosForUCAs", + "command": "pasta.checks.checkScenariosForUCAs", "title": "Set the scenarios for UCA check", "category": "STPA Checks" }, { - "command": "stpa.checks.checkSafetyRequirementsForUCAs", + "command": "pasta.checks.checkSafetyRequirementsForUCAs", "title": "Set the safety requirements for UCA check", "category": "STPA Checks" }, { - "command": "stpa.IDs.undo", + "command": "pasta.IDs.undo", "title": "Executes the undo action", "category": "STPA ID Enforcement" }, { - "command": "stpa.IDs.redo", + "command": "pasta.IDs.redo", "title": "Executes the redo action", "category": "STPA ID Enforcement" }, { - "command": "pasta.generate.cutSets", + "command": "pasta.generate.ftaCutSets", "title": "Generate the cut sets", "category": "Cut Sets" }, { - "command": "pasta.generate.minimalCutSets", + "command": "pasta.generate.ftaMinimalCutSets", "title": "Generate the minimal cut sets", "category": "Cut Sets" } @@ -208,59 +188,43 @@ "when": "editorLangId == 'stpa'" }, { - "command": "stpa.diagram.fit", + "command": "pasta.diagram.fit", "when": "stpa-diagram-focused" }, { - "command": "stpa.diagram.center", + "command": "pasta.diagram.center", "when": "stpa-diagram-focused" }, { - "command": "stpa.diagram.delete", + "command": "pasta.diagram.delete", "when": "stpa-diagram-focused" }, { - "command": "stpa.diagram.export", + "command": "pasta.diagram.export", "when": "stpa-diagram-focused" }, { - "command": "fta.diagram.fit", - "when": "fta-diagram-focused" - }, - { - "command": "fta.diagram.center", - "when": "fta-diagram-focused" - }, - { - "command": "fta.diagram.delete", - "when": "fta-diagram-focused" - }, - { - "command": "fta.diagram.export", - "when": "fta-diagram-focused" - }, - { - "command": "stpa.checks.setCheckResponsibilitiesForConstraints", + "command": "pasta.checks.setCheckResponsibilitiesForConstraints", "when": "editorLangId == 'stpa'" }, { - "command": "stpa.checks.checkConstraintsForUCAs", + "command": "pasta.checks.checkConstraintsForUCAs", "when": "editorLangId == 'stpa'" }, { - "command": "stpa.checks.checkScenariosForUCAs", + "command": "pasta.checks.checkScenariosForUCAs", "when": "editorLangId == 'stpa'" }, { - "command": "stpa.checks.checkSafetyRequirementsForUCAs", + "command": "pasta.checks.checkSafetyRequirementsForUCAs", "when": "editorLangId == 'stpa'" }, { - "command": "pasta.generate.cutSets", + "command": "pasta.generate.ftaCutSets", "when": "editorLangId == 'fta'" }, { - "command": "pasta.generate.minimalCutSets", + "command": "pasta.generate.ftaMinimalCutSets", "when": "editorLangId == 'fta'" } ], @@ -276,42 +240,48 @@ "group": "navigation" }, { - "submenu": "stpa.checks", + "submenu": "pasta.checks", "group": "checks" }, { - "command": "pasta.generate.cutSets", - "when": "editorLangId == 'fta'", - "group": "navigation" - }, - { - "command": "pasta.generate.minimalCutSets", - "when": "editorLangId == 'fta'", - "group": "navigation" + "submenu": "pasta.generate", + "group": "generate" } ], - "stpa.checks": [ + "pasta.checks": [ { - "command": "stpa.checks.setCheckResponsibilitiesForConstraints", + "command": "pasta.checks.setCheckResponsibilitiesForConstraints", "title": "editorLangId == 'stpa'", "group": "navigation" }, { - "command": "stpa.checks.checkConstraintsForUCAs", + "command": "pasta.checks.checkConstraintsForUCAs", "title": "editorLangId == 'stpa'", "group": "navigation" }, { - "command": "stpa.checks.checkScenariosForUCAs", + "command": "pasta.checks.checkScenariosForUCAs", "title": "editorLangId == 'stpa'", "group": "navigation" }, { - "command": "stpa.checks.checkSafetyRequirementsForUCAs", + "command": "pasta.checks.checkSafetyRequirementsForUCAs", "title": "editorLangId == 'stpa'", "group": "navigation" } ], + "pasta.generate": [ + { + "command": "pasta.generate.ftaCutSets", + "when": "editorLangId == 'fta'", + "group": "navigation" + }, + { + "command": "pasta.generate.ftaMinimalCutSets", + "when": "editorLangId == 'fta'", + "group": "navigation" + } + ], "editor/title": [ { "command": "pasta.diagram.open", @@ -336,12 +306,12 @@ "group": "navigation" }, { - "command": "pasta.generate.cutSets", + "command": "pasta.generate.ftaCutSets", "when": "resourceExtname == '.fta'", "group": "navigation" }, { - "command": "pasta.generate.minimalCutSets", + "command": "pasta.generate.ftaMinimalCutSets", "when": "resourceExtname == '.fta'", "group": "navigation" } @@ -349,45 +319,49 @@ }, "submenus": [ { - "id": "stpa.checks", + "id": "pasta.checks", "label": "Validation Checks" + }, + { + "id": "pasta.generate", + "label": "Generate cut sets" } ], "keybindings": [ { "key": "alt+f", "mac": "alt+f", - "command": "stpa.diagram.fit", + "command": "pasta.diagram.fit", "when": "stpa-diagram-focused" }, { "key": "alt+c", "mac": "alt+c", - "command": "stpa.diagram.center", + "command": "pasta.diagram.center", "when": "stpa-diagram-focused" }, { "key": "alt+e", "mac": "alt+e", - "command": "stpa.diagram.export", + "command": "pasta.diagram.export", "when": "stpa-diagram-focused" }, { "key": "delete", "mac": "delete", - "command": "stpa.diagram.delete", + "command": "pasta.diagram.delete", "when": "stpa-diagram-focused" }, { "key": "ctrl+z", "mac": "cmd+z", - "command": "stpa.IDs.undo", + "command": "pasta.IDs.undo", "when": "editorTextFocus" }, { "key": "ctrl+y", "mac": "cmd+y", - "command": "stpa.IDs.redo", + "command": "pasta.IDs.redo", "when": "editorTextFocus" } ] diff --git a/extension/src-language-server/fta.langium b/extension/src-language-server/fta.langium index 5d2fce3..e288492 100644 --- a/extension/src-language-server/fta.langium +++ b/extension/src-language-server/fta.langium @@ -16,22 +16,22 @@ Gate: name=ID "=" type=(AND|OR|KNGate|InhibitGate); TopEvent: - name=STRING "=" child+=[Child:ID]; + name=STRING "=" children+=[Children:ID]; -Child: +Children: Gate | Component; AND: - child+=[Child:ID] (string='and' child+=[Child:ID])+; + children+=[Children:ID] ('and' children+=[Children:ID])+; OR: - child+=[Child:ID] (string='or' child+=[Child:ID])+; + children+=[Children:ID] ('or' children+=[Children:ID])+; KNGate: - k=INT string='of' child+=[Child:ID] (',' child+=[Child:ID])+; + k=INT 'of' children+=[Children:ID] (',' children+=[Children:ID])+; InhibitGate: - condition+=[Condition:ID] string='inhibits' child+=[Child:ID]; + condition+=[Condition:ID] 'inhibits' children+=[Children:ID]; hidden terminal WS: /\s+/; terminal ID: /[_a-zA-Z][\w_]*/; diff --git a/extension/src-language-server/fta/fta-cutSet-generator.ts b/extension/src-language-server/fta/fta-cutSet-generator.ts index 975a703..06532bf 100644 --- a/extension/src-language-server/fta/fta-cutSet-generator.ts +++ b/extension/src-language-server/fta/fta-cutSet-generator.ts @@ -1,5 +1,24 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { AstNode } from 'langium'; +import { isAND, isComponent, isCondition, isGate, isInhibitGate, isKNGate, isOR, isTopEvent } from '../generated/ast'; import { FTAEdge, FTANode } from './fta-interfaces'; -import { FTAAspect } from "./fta-model"; +import { FTNodeType } from "./fta-model"; export class CutSetGenerator{ @@ -36,7 +55,6 @@ export class CutSetGenerator{ } } - return true; } /** @@ -46,9 +64,6 @@ export class CutSetGenerator{ * @returns A list of lists that that contains every cut set of the given Fault Tree. */ generateCutSets(allNodes:FTANode[], allEdges:FTAEdge[]):FTANode[][]{ - - - //Idea: //Start from the top event. //Get the only child of top event (will always be only one) as our starting node. @@ -56,27 +71,14 @@ export class CutSetGenerator{ //In the evaluation we check if the child has children too and do the same recursively until the children are components. //Depending on the type of the node process the results of the children differently. - //Order components by level from top to bottom for a smaller BDD. - allNodes.sort(this.sortByLevel); //When there is no gate, return the component const startingNode = this.getChildOfTopEvent(allNodes, allEdges); - if(startingNode.aspect === FTAAspect.COMPONENT){ + if(startingNode.nodeType === FTNodeType.COMPONENT){ return [[startingNode]]; } //Evaluate the child of the top event and recursively the entire Fault Tree. - const unprocressedCutSets = this.evaluate(startingNode, allNodes, allEdges); - - //Final duplication checks: - //In the case that two gates share the same child, remove duplicates from all innerLists. [[C,U,U]] -> [[C,U]] - const tempCutSets:FTANode[][] = []; - for(const innerList of unprocressedCutSets){ - const filteredList = innerList.filter((e,i) => innerList.indexOf(e) === i); //indexOf only returns the first index of the element in the list. - tempCutSets.push(filteredList); - } - - //In case there are two inner lists with the same elements, remove the duplicates. [[C,U], [U,C]] -> [[C,U]] - const cutSets = tempCutSets.filter((e,i) => this.indexOfArray(e, tempCutSets) === i); + const cutSets = this.evaluate(startingNode, allNodes, allEdges); return cutSets; @@ -97,18 +99,18 @@ export class CutSetGenerator{ if(children.length === 0){return result;}; //if the node is an and/inhibit-gate we want to evaluate all children and concatenate all inner lists of one child with another. - if(node.aspect === FTAAspect.AND || node.aspect === FTAAspect.INHIBIT){ + if(node.nodeType === FTNodeType.AND || node.nodeType === FTNodeType.INHIBIT){ for(const child of children){ - if(child.aspect === FTAAspect.COMPONENT || child.aspect === FTAAspect.CONDITION){ - result = this.f([[child]], result); + if(child.nodeType === FTNodeType.COMPONENT || child.nodeType === FTNodeType.CONDITION){ + result = this.concatAllLists([[child]], result); }else{ - result = this.f(this.evaluate(child, allNodes, allEdges), result); + result = this.concatAllLists(this.evaluate(child, allNodes, allEdges), result); } } //if the node is an or-gate we want to evaluate all children and add every single inner list to the result. - }else if(node.aspect === FTAAspect.OR){ + }else if(node.nodeType === FTNodeType.OR){ for(const child of children){ - if(child.aspect === FTAAspect.COMPONENT){ + if(child.nodeType === FTNodeType.COMPONENT){ const orList = [child]; result.push(orList); }else{ @@ -117,13 +119,10 @@ export class CutSetGenerator{ } } } - //Above we say an or-gate fails when exactly one child fails but multiple children can fail too. - result = this.generatePowerSet(result); - result.splice(0,1); // Remove empty set //if the node is a kN-gate we want to get every combinations of the children with length k and after that evaluate the gates in the list. - }else if(node.aspect === FTAAspect.KN){ + }else if(node.nodeType === FTNodeType.KN){ const k = node.k as number; const n = node.n as number; @@ -138,7 +137,7 @@ export class CutSetGenerator{ //Now we want to evaluate G1 (e.g evaluation(G1) = [[C]]). //Our result list should look like this -> [[M1,M2], [M1,C], [M2,C]]. for(const comb of combinations){ - if(comb.some(e => e.aspect === FTAAspect.AND || e.aspect === FTAAspect.INHIBIT || e.aspect === FTAAspect.OR || e.aspect === FTAAspect.KN)){ + if(comb.some(e => e.nodeType === FTNodeType.AND || e.nodeType === FTNodeType.INHIBIT || e.nodeType === FTNodeType.OR || e.nodeType === FTNodeType.KN)){ const evaluatedLists = this.evaluateGateInCombinationList(comb, allNodes, allEdges); for(const list of evaluatedLists){ result.push(list); @@ -168,13 +167,13 @@ export class CutSetGenerator{ for(let i = 0; i { - return theArray.reduce((subsets: FTANode[][], values: FTANode[]) => { - const newSubsets: FTANode[][] = []; - - - for (const subset of subsets) { - let newSet = values.concat(...subset); - newSet = newSet.filter((e,i) => newSet.indexOf(e) === i); - if(this.indexOfArray(newSet, subsets) === -1 && this.indexOfArray(newSet, newSubsets) === -1){ - newSubsets.push(newSet); - } - - } - - - return subsets.concat(newSubsets); - }, [[]]); - }; - - return getPowerSet(sets); - } - - - /** - * Gets a node with its id from all nodes. - * @param nodes All FtaNodes in the graph. - * @param id The id of Node. - * @returns an FTANode with the given id. - */ - getNodeWithID(nodes: FTANode[], id: String): FTANode{ - for(const node of nodes){ - if(node.id === id){ - return node; - } - } - const empty = {} as FTANode; - return empty; - } /** * Take an FTANode and return all its children(just the next hierarchy level). * @param parentNode The node we want the children of. @@ -280,36 +232,18 @@ export class CutSetGenerator{ */ getAllChildrenOfNode(parentNode: FTANode, allNodes: FTANode[], allEdges:FTAEdge[]): FTANode[]{ const children: FTANode[] = []; + for(const edge of allEdges){ if(parentNode.id === edge.sourceId){ - children.push(this.getNodeWithID(allNodes, edge.targetId)); + const child = allNodes.find(node => node.id === edge.targetId); + children.push(child as FTANode); } } return children; } - /** - * Sort condition so that components higher in the graph(lower level) will be processed first in the fta node array. - * @param a The first FTANode to compare. - * @param b The second FTANode to compare. - * @returns The order of both nodes with the lower level one being first. - */ - sortByLevel(a: FTANode, b: FTANode): number{ - if(a.level && b.level){ - if(a.level < b.level){ - return -1; - }else if(a.level > b.level){ - return 1; - }else if(a.id < b.id){ - return -1; - }else if(a.id > b.id){ - return 1; - } - return 0; - - } - return 0; - } + + /** * Given all Nodes this method returns the first and only child of the topevent. * @param nodes All FtaNodes in the graph. @@ -317,15 +251,11 @@ export class CutSetGenerator{ * @returns the child of the topevent. */ getChildOfTopEvent(allNodes:FTANode[], allEdges:FTAEdge[]): FTANode{ - let topEvent:FTANode = {} as FTANode; - for(const node of allNodes){ - if(node.aspect === FTAAspect.TOPEVENT){ - topEvent = node; - } - } + const topEvent:FTANode = allNodes.find(node => node.nodeType === FTNodeType.TOPEVENT) as FTANode; + for(const edge of allEdges){ if(edge.sourceId === topEvent.id){ - return this.getNodeWithID(allNodes, edge.targetId); + return (allNodes.find(node => node.id === edge.targetId)) as FTANode; } } @@ -338,7 +268,7 @@ export class CutSetGenerator{ * @param b The second two-dimensional FTANode array. * @returns a two-dimensional array of type FTANode where every innerList of both arrays is concatenated. */ - f(a:FTANode[][], b:FTANode[][]):FTANode[][]{ + concatAllLists(a:FTANode[][], b:FTANode[][]):FTANode[][]{ const result: FTANode[][] = []; if(a.length === 0){ @@ -370,8 +300,8 @@ export class CutSetGenerator{ * @returns True if they are equal and false if not. */ arrayEquals(a:FTANode[], b:FTANode[]):boolean{ - const sortedA = a.sort((x,y) => this.sortByLevel(x,y)); - const sortedB = b.sort((x,y) => this.sortByLevel(x,y)); + const sortedA = a.sort((x,y) => (x.id > y.id ? -1 : 1)); + const sortedB = b.sort((x,y) => (x.id > y.id ? -1 : 1)); return a.length === b.length && sortedA.every((e,i) => e === sortedB[i]); } @@ -395,4 +325,274 @@ export class CutSetGenerator{ return i; } +//---------------------------- + + determineMinimalCutSetAst(allNodes:AstNode[]):AstNode[][]{ + const bdd = this.generateCutSetsAst(allNodes); + + //Cut sets are minimal if, when any basic event is removed from the set, the remaining events collectively are no longer a cut set. + //Check every innerList + //If inner list contains another array from the bdd array, remove innerList because it cant be a minimal cut set + const minimalCutSet = bdd.filter(innerList => { + return this.checkIfMinimalCutSetAst(innerList, bdd); //if this condition is true then the innerList is a minimal cut set + }); + + return minimalCutSet; + } + + checkIfMinimalCutSetAst(innerList:AstNode[], bdd:AstNode[][]):boolean{ + for(const list of bdd){ + if(list.every(e=>innerList.includes(e)) && innerList !== list){ + return false; + } + } + + return true; + } + + generateCutSetsAst(allNodes:AstNode[]):AstNode[][]{ + + + //When there is no gate, return the component + const startingNode = this.getChildOfTopEventAst(allNodes); + if(isComponent(startingNode)){ + return [[startingNode]]; + } + //Evaluate the child of the top event and recursively the entire Fault Tree. + const cutSets = this.evaluateAst(startingNode, allNodes); + + return cutSets; + + } + + evaluateAst(node:AstNode, allNodes: AstNode[]): AstNode[][]{ + let result:AstNode[][] = []; + + // we start with the top-most gate(child of topevent) and get all its children. + const children = this.getAllChildrenOfNodeAst(node); + if(children.length === 0){return result;}; + + //if the node is an and/inhibit-gate we want to evaluate all children and concatenate all inner lists of one child with another. + if(isGate(node) && (isAND(node.type) || isInhibitGate(node.type))){ + for(const child of children){ + if(isComponent(child) || isCondition(child)){ + result = this.concatAllListsAst([[child]], result); + }else{ + result = this.concatAllListsAst(this.evaluateAst(child, allNodes), result); + } + } + //if the node is an or-gate we want to evaluate all children and add every single inner list to the result. + }else if(isGate(node) && isOR(node.type)){ + for(const child of children){ + if(isComponent(child)){ + const orList = [child]; + result.push(orList); + }else{ + for(const list of this.evaluateAst(child, allNodes)){ //push every inner list of the child gate. + result.push(list); + } + } + } + + + + //if the node is a kN-gate we want to get every combinations of the children with length k and after that evaluate the gates in the list. + }else if(isGate(node) && isKNGate(node.type)){ + const k = node.type.k as number; + const n = node.type.children.length as number; + + //Example: With Children:[M1,M2,G1] and k=2 -> [[M1,M2],[M1,G1],[M2,G1]] . + const combinations:AstNode[][]=[]; + for(let i = k; i<=n; i++){ + for(const comb of this.getAllCombinationsAst(children, i)){ + combinations.push(comb); + } + } + + //Now we want to evaluate G1 (e.g evaluation(G1) = [[C]]). + //Our result list should look like this -> [[M1,M2], [M1,C], [M2,C]]. + for(const comb of combinations){ + if(comb.some(e => isGate(e) && (isAND(e.type) || isInhibitGate(e.type) || isOR(e.type) || isKNGate(e.type)))){ + const evaluatedLists = this.evaluateGateInCombinationListAst(comb, allNodes); + for(const list of evaluatedLists){ + result.push(list); + } + }else{ + result.push(comb); + } + } + } + + return result; + + } + + evaluateGateInCombinationListAst(innerList: AstNode[], allNodes:AstNode[]):AstNode[][]{ + + let result:AstNode[][] = []; + const restList:AstNode[] = innerList; + + for(let i = 0; i nodes.length || k <= 0) { + return []; + } + if (k === nodes.length) { + return [nodes]; + } + if(k===1){ + for(let i = 0; i newSet.indexOf(e) === i); + if(this.indexOfArrayAst(newSet, result) === -1){ + result.push(newSet); + } + + + } + } + + return result; + + } + + arrayEqualsAst(a:AstNode[], b:AstNode[]):boolean{ + /* const idCache = args.idCache; + const sort = (x:AstNode, y:AstNode):number => { + let idX = idCache.getId(x); + let idY = idCache.getId(y); + if(idX && idY){ + return idX > idY ? -1 : 1; + } + return 0; + } + const sortA = a.sort(sort); + const sortB = b.sort(sort); */ + + + return a.length === b.length && a.every((e,i) => e === b[i]); + + + + } + + + indexOfArrayAst(a:AstNode[], b:AstNode[][]):number{ + let i = 0; + for(const list of b){ + if(this.arrayEqualsAst(a, list)){ + break; + } + i++; + } + if(i >= b.length){ + return -1; + } + return i; + } + } + + + + diff --git a/extension/src-language-server/fta/fta-diagram-generator.ts b/extension/src-language-server/fta/fta-diagram-generator.ts index b2a6ab8..e46cb46 100644 --- a/extension/src-language-server/fta/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/fta-diagram-generator.ts @@ -1,23 +1,40 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + import { AstNode } from 'langium'; import { GeneratorContext, LangiumDiagramGenerator } from 'langium-sprotty'; import { SLabel, SModelElement, SModelRoot } from 'sprotty-protocol'; -import { ModelFTA, isComponent, isCondition, isGate, isKNGate, isTopEvent } from '../generated/ast'; +import { Component, Condition, Gate, ModelFTA, TopEvent, isComponent, isCondition, isGate, isKNGate } from '../generated/ast'; import { FTAEdge, FTANode } from './fta-interfaces'; -import { FTA_EDGE_TYPE, FTA_NODE_TYPE, PARENT_TYPE } from './fta-model'; +import { FTA_EDGE_TYPE, FTA_NODE_TYPE, TREE_TYPE } from './fta-model'; import { FtaServices } from './fta-module'; -import { getAllGateTypes, getAspect, getTargets, setLevelsForFTANodes } from './fta-utils'; +import { getAllGateTypes, getFTNodeType, getTargets } from './fta-utils'; export class FtaDiagramGenerator extends LangiumDiagramGenerator{ - allNodes:FTANode[]; - allEdges:FTAEdge[]; + allNodes:AstNode[]; + //allEdges:FTAEdge[]; constructor(services: FtaServices){ super(services); } /** - * Generates a SGraph for the FTA model contained in {@code args}. + * Generates an SGraph for the FTA model contained in {@code args}. * @param args GeneratorContext for the FTA model. * @returns the root of the generated SGraph. */ @@ -25,63 +42,66 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ const { document } = args; const model: ModelFTA = document.parseResult.value; //set filter for later maybe - const filteredModelFTA = model; - let ftaChildren: SModelElement[] = filteredModelFTA.components?.map(l => this.generateFTANode(l, args)); + let ftaChildren: SModelElement[] = model.components?.map(comps => this.generateFTANode(comps, args)); - //returns an array of arrays with with every and,or,kn,inhibit-gate at position 1 to 4. - const allGates = getAllGateTypes(filteredModelFTA.gates); + //returns a Map with the gate types as the key and all instances of that type as the value. + const allGates: Map = getAllGateTypes(model.gates); + //first create the ftaNode for the topevent, conditions and all gates ftaChildren = ftaChildren.concat([ - //first create the ftaNode for the topevent, conditions and all gates - this.generateFTANode(filteredModelFTA.topEvent, args), - ...filteredModelFTA.conditions?.map(c => this.generateFTANode(c,args)).flat(1), - - ...allGates[0]?.map(a => this.generateFTANode(a, args)).flat(1), //and - ...allGates[1]?.map(o => this.generateFTANode(o, args)).flat(1), //or - ...allGates[2]?.map(k => this.generateFTANode(k, args)).flat(1), //kn - ...allGates[3]?.map(i => this.generateFTANode(i, args)).flat(1), //inhib + this.generateFTANode(model.topEvent, args), + ...model.conditions?.map(cond => this.generateFTANode(cond,args)).flat(1) + ]); + + allGates.forEach((value:AstNode[]) => { + ftaChildren = ftaChildren.concat([ + ...value?.map(gates => this.generateFTANode(gates as Gate,args)).flat(1), + ]); + }); + //after that create the edges of the gates and the top event + allGates.forEach((value:AstNode[]) => { + ftaChildren = ftaChildren.concat([ + ...value?.map(gates => this.generateEdgesForFTANode(gates,args)).flat(1), + ]); + }); - //after that create the edges of the gates and the top event - ...allGates[0]?.map(a => this.generateAspectWithEdges(a, args)).flat(1), - ...allGates[1]?.map(o => this.generateAspectWithEdges(o, args)).flat(1), - ...allGates[2]?.map(k => this.generateAspectWithEdges(k, args)).flat(1), - ...allGates[3]?.map(i => this.generateAspectWithEdges(i, args)).flat(1), - ...this.generateAspectWithEdges(filteredModelFTA.topEvent, args), + ftaChildren = ftaChildren.concat([...this.generateEdgesForFTANode(model.topEvent, args),]); - ]); - // filtering the nodes of the FTA graph - const ftaNodes: FTANode[] = []; + // filtering the nodes of the FTA graph + /* const ftaNodes: FTANode[] = []; for (const node of ftaChildren) { if (node.type === FTA_NODE_TYPE) { ftaNodes.push(node as FTANode); } } - - const ftaEdges: FTAEdge[] = []; + const ftaEdges:FTAEdge[] = []; for(const edge of ftaChildren){ if(edge.type === FTA_EDGE_TYPE){ ftaEdges.push(edge as FTAEdge); } - } - + } this.allNodes = ftaNodes; - this.allEdges = ftaEdges; - // give the top event the level 0 + this.allEdges = ftaEdges; */ + + this.allNodes = model.components; + this.allNodes = this.allNodes.concat(model.topEvent, ...model.conditions); + allGates.forEach((value:AstNode[]) => { + this.allNodes = this.allNodes.concat(...value); + }); - setLevelsForFTANodes(ftaNodes, ftaEdges); return { type: 'graph', id: 'root', children: [ { - type: PARENT_TYPE, - id: 'relationships', + type: TREE_TYPE, + id: 'faultTree', children: ftaChildren } ] @@ -91,35 +111,21 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ /** * Getter method for every FTANode in the Fault Tree. - * @returns Every FTANode in the Fault Tree. + * @returns every FTANode in the Fault Tree. */ - public getNodes():FTANode[]{ + public getNodes():AstNode[]{ return this.allNodes; } - /** - * Getter method for every FTAEdge in the Fault Tree. - * @returns Every FTAEdge in the Fault Tree. - */ - public getEdges():FTAEdge[]{ + /* public getEdges():FTAEdge[]{ return this.allEdges; - } - - /** - * Generates the edges for the given {@code node}. - * @param node FTA component for which edges should be generated. - * @param args GeneratorContext of the FTA model. - * @returns Edges representing the references {@code node} contains. - */ - private generateAspectWithEdges(node: AstNode, args: GeneratorContext): SModelElement[] { - const elements: SModelElement[] = this.generateEdgesForFTANode(node, args); - return elements; - } + } */ + /** * Generates the edges for {@code node}. * @param node FTA component for which the edges should be created. * @param args GeneratorContext of the FTA model. - * @returns Edges representing the references {@code node} contains. + * @returns edges representing the references {@code node} contains. */ private generateEdgesForFTANode(node: AstNode, args: GeneratorContext): SModelElement[] { const idCache = args.idCache; @@ -146,7 +152,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ * @param targetId The ID of the target of the edge. * @param label The label of the edge. * @param param4 GeneratorContext of the FTA model. - * @returns An FTAEdge. + * @returns an FTAEdge. */ private generateFTAEdge(edgeId: string, sourceId: string, targetId: string, label: string, { idCache }: GeneratorContext): FTAEdge { let children: SModelElement[] = []; @@ -164,7 +170,8 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ id: edgeId, sourceId: sourceId, targetId: targetId, - children: children + children: children, + highlight: true, }; } @@ -172,62 +179,62 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ * Generates a single FTANode for the given {@code node}. * @param node The FTA component the node should be created for. * @param args GeneratorContext of the FTA model. - * @returns A FTANode representing {@code node}. + * @returns a FTANode representing {@code node}. */ - private generateFTANode(node: AstNode, args: GeneratorContext): FTANode { + private generateFTANode(node: TopEvent | Gate | Component | Condition, args: GeneratorContext): FTANode { const idCache = args.idCache; - if(isTopEvent(node) || isGate(node) || isComponent(node) || isCondition(node)){ - const nodeId = idCache.uniqueId(node.name, node); - - const children: SModelElement[] = [ - { - type: 'label', - id: idCache.uniqueId(nodeId + '.label'), - text: node.name - } - ]; + + const nodeId = idCache.uniqueId(node.name, node); - let desc = ""; - if(isComponent(node) || isCondition(node)){ - desc = node.description; + const children: SModelElement[] = [ + { + type: 'label', + id: idCache.uniqueId(nodeId + '.label'), + text: node.name } + ]; + + let desc = ""; + if(isComponent(node) || isCondition(node)){ + desc = node.description; + } - if(isGate(node) && isKNGate(node.type)){ - return { - type: FTA_NODE_TYPE, - id: nodeId, - aspect: getAspect(node), - description: desc, - children: children, - k: node.type.k, - n: node.type.child.length, - layout: 'stack', - layoutOptions: { - paddingTop: 10.0, - paddingBottom: 10.0, - paddngLeft: 10.0, - paddingRight: 10.0 - } - }; - }else{ - return { - type: FTA_NODE_TYPE, - id: nodeId, - aspect: getAspect(node), - description: desc, - children: children, - layout: 'stack', - layoutOptions: { - paddingTop: 10.0, - paddingBottom: 10.0, - paddngLeft: 10.0, - paddingRight: 10.0 - } - }; - } - }else { - throw new Error("generateFTANode method should only be called with an FTA component"); + if(isGate(node) && isKNGate(node.type)){ + return { + type: FTA_NODE_TYPE, + id: nodeId, + nodeType: getFTNodeType(node), + description: desc, + children: children, + highlight: true, + k: node.type.k, + n: node.type.children.length, + layout: 'stack', + layoutOptions: { + paddingTop: 10.0, + paddingBottom: 10.0, + paddngLeft: 10.0, + paddingRight: 10.0 + } + }; + }else{ + return { + type: FTA_NODE_TYPE, + id: nodeId, + nodeType: getFTNodeType(node), + description: desc, + children: children, + highlight: true, + layout: 'stack', + layoutOptions: { + paddingTop: 10.0, + paddingBottom: 10.0, + paddngLeft: 10.0, + paddingRight: 10.0 + } + }; } + } } \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-diagramServer.ts b/extension/src-language-server/fta/fta-diagramServer.ts deleted file mode 100644 index 174064f..0000000 --- a/extension/src-language-server/fta/fta-diagramServer.ts +++ /dev/null @@ -1,99 +0,0 @@ -/* - * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient - * - * http://rtsys.informatik.uni-kiel.de/kieler - * - * Copyright 2022 by - * + Kiel University - * + Department of Computer Science - * + Real-Time and Embedded Systems Group - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - */ - -import { Action, DiagramServer, DiagramServices, RequestAction, RequestModelAction, ResponseAction } from 'sprotty-protocol'; -import { UpdateViewAction } from '../actions'; -import { UpdateOptionsAction } from '../options/actions'; -import { FtaSynthesisOptions } from './fta-synthesis-options'; - -export class FtaDiagramServer extends DiagramServer { - - protected ftaOptions: FtaSynthesisOptions; - clientId: string; - - constructor(dispatch:
(action: A) => Promise, - services: DiagramServices, clientId: string) { - super(dispatch, services); - this.clientId = clientId; - } - - accept(action: Action): Promise { - console.log("received from client: " + action.kind); - return super.accept(action); - } - - request(action: RequestAction): Promise { - console.log("request send from server to client: " + action.kind); - return super.request(action); - } - - protected handleAction(action: Action): Promise { - switch (action.kind) { - // case SetSynthesisOptionsAction.KIND: - // return this.handleSetSynthesisOption(action as SetSynthesisOptionsAction); - case UpdateViewAction.KIND: - return this.handleUpdateView(action as UpdateViewAction); - } - return super.handleAction(action); - } - - /* - protected handleSetSynthesisOption(action: SetSynthesisOptionsAction): Promise { - for (const option of action.options) { - const opt = this.ftaOptions.getSynthesisOptions().find(synOpt => synOpt.synthesisOption.id === option.id); - if (opt) { - opt.currentValue = option.currentValue; - // for dropdown menu options more must be done - if ((opt.synthesisOption as DropDownOption).currentId) { - (opt.synthesisOption as DropDownOption).currentId = option.currentValue; - this.dispatch({ kind: UpdateOptionsAction.KIND, valuedSynthesisOptions: this.ftaOptions.getSynthesisOptions(), clientId: this.clientId }); - } - } - } - const updateAction = { - kind: UpdateViewAction.KIND, - options: this.state.options - } as UpdateViewAction; - this.handleUpdateView(updateAction); - return Promise.resolve(); - } - */ - - protected async handleUpdateView(action: UpdateViewAction): Promise { - this.state.options = action.options; - try { - const newRoot = await this.diagramGenerator.generate({ - options: this.state.options ?? {}, - state: this.state - }); - newRoot.revision = ++this.state.revision; - this.state.currentRoot = newRoot; - await this.submitModel(this.state.currentRoot, true, action); - // ensures the the filterUCA option is correct - this.dispatch({ kind: UpdateOptionsAction.KIND, clientId: this.clientId }); - } catch (err) { - this.rejectRemoteRequest(action, err as Error); - console.error('Failed to generate diagram:', err); - } - } - - protected async handleRequestModel(action: RequestModelAction): Promise { - await super.handleRequestModel(action); - this.dispatch({ kind: UpdateOptionsAction.KIND, clientId: this.clientId }); - } - -} \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-interfaces.ts b/extension/src-language-server/fta/fta-interfaces.ts index 4fff483..621fa68 100644 --- a/extension/src-language-server/fta/fta-interfaces.ts +++ b/extension/src-language-server/fta/fta-interfaces.ts @@ -1,37 +1,39 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + import { SEdge, SNode } from "sprotty-protocol"; -import { FTAAspect } from "./fta-model"; +import { FTNodeType } from "./fta-model"; /** * Node representing a FTA component. */ export interface FTANode extends SNode{ - aspect: FTAAspect, + nodeType: FTNodeType, description: string highlight?: boolean - level?: number k?: number n?: number } /** - * Edge representing an edge in the relationship graph. + * Edge representing an edge in the fault Tree. */ export interface FTAEdge extends SEdge { highlight?: boolean -} - -/** - * Node representing a system component in the BDD. - */ -export interface BDDNode extends SNode { - level?: number -} - -/** - * Edge representing component failure in the BDD. - */ -export interface BDDEdge extends SEdge { - fail?: boolean } \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-layout-config.ts b/extension/src-language-server/fta/fta-layout-config.ts index 9893d3b..d4138c6 100644 --- a/extension/src-language-server/fta/fta-layout-config.ts +++ b/extension/src-language-server/fta/fta-layout-config.ts @@ -1,7 +1,23 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + import { LayoutOptions } from 'elkjs'; import { DefaultLayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; import { SGraph, SModelIndex, SNode } from 'sprotty-protocol'; -import { FTANode } from './fta-interfaces'; export class FtaLayoutConfigurator extends DefaultLayoutConfigurator { @@ -9,29 +25,20 @@ export class FtaLayoutConfigurator extends DefaultLayoutConfigurator { protected graphOptions(sgraph: SGraph, index: SModelIndex): LayoutOptions { //options for the entire graph. return { - 'org.eclipse.elk.partitioning.activate': 'true', 'org.eclipse.elk.spacing.nodeNode': '30.0', - 'org.eclipse.elk.direction': 'DOWN', - 'org.eclipse.elk.layered.spacing.edgeNodeBetweenLayers': '30.0', - 'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': '30.0', + 'org.eclipse.elk.direction': 'DOWN', }; } protected nodeOptions(snode: SNode, index: SModelIndex): LayoutOptions | undefined { //options for the nodes. - const level = (snode as FTANode).level; return { - 'org.eclipse.elk.layered.thoroughness': '70', - 'org.eclipse.elk.partitioning.activate': 'true', 'org.eclipse.elk.nodeLabels.placement': "INSIDE V_CENTER H_CENTER", - 'org.eclipse.elk.partitioning.partition': "" + level, - 'org.eclipse.elk.layered.nodePlacement.layerConstraint': "" + level, - + //'org.eclipse.elk.nodeSize.constraints': 'NODE_LABELS', 'org.eclipse.elk.direction' : 'DOWN', - 'org.eclipse.elk.algorithm': 'layered', 'org.eclipse.elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX', - 'org.eclipse.elk.spacing.portsSurrounding': '[top=10.0,left=10.0,bottom=10.0,right=10.0]' + }; } } \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index 47c4c39..353bd08 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -1,53 +1,66 @@ -import { Connection, URI } from "vscode-languageserver"; -import { FtaServices } from './fta-module'; -import { LangiumSprottySharedServices } from "langium-sprotty"; -import { FtaDiagramGenerator } from "./fta-diagram-generator"; - +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ +import { IdCache, LangiumSprottySharedServices } from 'langium-sprotty'; +import { Connection } from "vscode-languageserver"; +import { FtaDiagramGenerator } from "./fta-diagram-generator"; +import { FtaServices } from './fta-module'; +import { AstNode } from 'langium'; -let lastUri: URI; /** * Adds handlers for notifications regarding fta. * @param connection - * @param stpaServices + * @param ftaServices */ -export function addFTANotificationHandler(connection: Connection, ftaServices: FtaServices, sharedServices: LangiumSprottySharedServices): void { - addGenerateCutSetsHandler(connection, ftaServices); - addGenerateMinimalCutSetsHandler(connection, ftaServices); +export function addFTANotificationHandler(connection: Connection, ftaServices: FtaServices): void { + addCutSetsHandler(connection, ftaServices); + } + /** * Adds handlers for requests regarding the cut sets. * @param connection * @param ftaServices */ -function addGenerateCutSetsHandler(connection: Connection, ftaServices: FtaServices):void{ - connection.onRequest('generate/getCutSets', uri =>{ - lastUri = uri; +function addCutSetsHandler(connection: Connection, ftaServices: FtaServices):void{ + connection.onRequest('generate/getCutSets', () =>{ + + const diagramGenerator = (ftaServices.diagram.DiagramGenerator) as FtaDiagramGenerator; const nodes = diagramGenerator.getNodes(); - const edges = diagramGenerator.getEdges(); + //const edges = diagramGenerator.getEdges(); + + + const cutSets = ftaServices.bdd.Bdd.generateCutSetsAst(nodes); + - const cutSets = ftaServices.bdd.Bdd.generateCutSets(nodes, edges); return cutSets; }); - -} -/** - * Adds handlers for requests regarding the minimal cut sets. - * @param connection - * @param ftaServices - */ -function addGenerateMinimalCutSetsHandler(connection: Connection, ftaServices: FtaServices):void{ - connection.onRequest('generate/getMinimalCutSets', uri =>{ - lastUri = uri; + connection.onRequest('generate/getMinimalCutSets', () =>{ const diagramGenerator = (ftaServices.diagram.DiagramGenerator) as FtaDiagramGenerator; const nodes = diagramGenerator.getNodes(); - const edges = diagramGenerator.getEdges(); - const minimalCutSets = ftaServices.bdd.Bdd.determineMinimalCutSet(nodes, edges); - return minimalCutSets; + //const edges = diagramGenerator.getEdges(); + + + /* const minimalCutSets = ftaServices.bdd.Bdd.determineMinimalCutSet(nodes, edges); + return minimalCutSets; */ }); } diff --git a/extension/src-language-server/fta/fta-model.ts b/extension/src-language-server/fta/fta-model.ts index dcef7f1..d84d524 100644 --- a/extension/src-language-server/fta/fta-model.ts +++ b/extension/src-language-server/fta/fta-model.ts @@ -1,16 +1,31 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + //diagram elements export const FTA_NODE_TYPE = 'node:fta'; -export const PARENT_TYPE= 'node:parent'; -export const EDGE_TYPE = 'edge'; export const FTA_EDGE_TYPE = 'edge:fta'; -export const BDD_NODE_TYPE = 'node:bdd'; -export const BDD_EDGE_TYPE = 'edge:bdd'; +export const TREE_TYPE = 'node:tree'; + /** - * The different aspects of FTA. + * The different types of nodes of FTA. */ -export enum FTAAspect { +export enum FTNodeType { TOPEVENT, COMPONENT, CONDITION, diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index 6fc697f..1d30d0f 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -1,16 +1,30 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ import ElkConstructor from 'elkjs/lib/elk.bundled'; -import { createDefaultModule, createDefaultSharedModule, DefaultSharedModuleContext, inject, Module, PartialLangiumServices } from 'langium'; +import { Module, PartialLangiumServices } from 'langium'; import { DefaultDiagramServerManager, DiagramActionNotification, LangiumSprottyServices, LangiumSprottySharedServices, SprottyDiagramServices, SprottySharedServices } from 'langium-sprotty'; import { DefaultElementFilter, ElkFactory, ElkLayoutEngine, IElementFilter, ILayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; import { DiagramOptions } from 'sprotty-protocol'; import { URI } from 'vscode-uri'; -import { FtaGeneratedModule, StpaGeneratedSharedModule } from '../generated/module'; +import { StpaDiagramServer } from '../stpa/stpa-diagramServer'; import { CutSetGenerator } from './fta-cutSet-generator'; import { FtaDiagramGenerator } from './fta-diagram-generator'; -import { FtaDiagramServer } from './fta-diagramServer'; import { FtaLayoutConfigurator } from './fta-layout-config'; -import { FtaSynthesisOptions } from './fta-synthesis-options'; import { FtaValidationRegistry, FtaValidator } from './fta-validator'; @@ -32,9 +46,6 @@ export type FtaAddedServices = { ElementFilter: IElementFilter, LayoutConfigurator: ILayoutConfigurator; }, - options: { - FtaSynthesisOptions: FtaSynthesisOptions - }, bdd: { Bdd: CutSetGenerator } @@ -65,16 +76,13 @@ export const FtaModule: Module new DefaultElementFilter, LayoutConfigurator: () => new FtaLayoutConfigurator }, - options: { - FtaSynthesisOptions: () => new FtaSynthesisOptions() - }, bdd:{ Bdd: () => new CutSetGenerator() } }; export const ftaDiagramServerFactory = - (services: LangiumSprottySharedServices): ((clientId: string, options?: DiagramOptions) => FtaDiagramServer) => { + (services: LangiumSprottySharedServices): ((clientId: string, options?: DiagramOptions) => StpaDiagramServer) => { const connection = services.lsp.Connection; const serviceRegistry = services.ServiceRegistry; return (clientId, options) => { @@ -86,7 +94,7 @@ export const ftaDiagramServerFactory = if (!language.diagram) { throw new Error(`The '${language.LanguageMetaData.languageId}' language does not support diagrams.`); } - return new FtaDiagramServer(async action => { + return new StpaDiagramServer(async action => { connection?.sendNotification(DiagramActionNotification.type, { clientId, action }); }, language.diagram, clientId); }; @@ -100,36 +108,5 @@ export const FtaSprottySharedModule: Module new DefaultDiagramServerManager(services) } }; -/** - * Create the full set of services required by Langium. - * - * First inject the shared services by merging two modules: - * - Langium default shared services - * - Services generated by langium-cli - * - * Then inject the language-specific services by merging three modules: - * - Langium default language-specific services - * - Services generated by langium-cli - * - Services specified in this file - * - * @param context Optional module context with the LSP connection - * @returns An object wrapping the shared services and the language-specific services - */ -export function createFtaServices(context: DefaultSharedModuleContext): { - shared: LangiumSprottySharedServices, - fta: FtaServices -} { - const shared = inject( - createDefaultSharedModule(context), - StpaGeneratedSharedModule, - FtaSprottySharedModule - ); - const fta = inject( - createDefaultModule({ shared }), - FtaGeneratedModule, - FtaModule - ); - shared.ServiceRegistry.register(fta); - return { shared, fta }; -} + diff --git a/extension/src-language-server/fta/fta-synthesis-options.ts b/extension/src-language-server/fta/fta-synthesis-options.ts deleted file mode 100644 index 5c12693..0000000 --- a/extension/src-language-server/fta/fta-synthesis-options.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ValuedSynthesisOption } from "../options/option-models"; - -export class FtaSynthesisOptions { - - private options: ValuedSynthesisOption[]; - - constructor() { - this.options = []; - } - - getSynthesisOptions(): ValuedSynthesisOption[] { - return this.options; - } - -} \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-utils.ts b/extension/src-language-server/fta/fta-utils.ts index fee972e..186fb16 100644 --- a/extension/src-language-server/fta/fta-utils.ts +++ b/extension/src-language-server/fta/fta-utils.ts @@ -1,7 +1,24 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + import { AstNode } from 'langium'; import { Gate, isAND, isComponent, isCondition, isGate, isInhibitGate, isKNGate, isOR, isTopEvent } from '../generated/ast'; -import { FTAEdge, FTANode } from './fta-interfaces'; -import { FTAAspect } from './fta-model'; +import { FTANode } from './fta-interfaces'; +import { FTNodeType } from './fta-model'; @@ -9,171 +26,112 @@ import { FTAAspect } from './fta-model'; /** * Getter for the references contained in {@code node}. - * @param node The FTAAspect which tracings should be returned. + * @param node The AstNode we want the children of. * @returns The objects {@code node} is traceable to. */ export function getTargets(node: AstNode): AstNode[] { - if (node) { - const targets: AstNode[] = []; - if(isTopEvent(node)){ - for (const ref of node.child) { - if (ref?.ref) { targets.push(ref.ref); } - } - }else if(isGate(node)){ - for(const ref of node.type.child){ - if(ref?.ref){targets.push(ref.ref);} //G3 = G4 AND G5 Referencen von G3: ref?.ref gelten wahrscheinlich als undefined - } - if(node.type.$type === 'InhibitGate'){ - for(const ref of node.type.condition){ - if(ref?.ref){targets.push(ref.ref);} - } + const targets: AstNode[] = []; + if(isTopEvent(node)){ + for (const ref of node.children) { + if (ref?.ref) { targets.push(ref.ref); } + } + }else if(isGate(node)){ + for(const ref of node.type.children){ + if(ref?.ref){targets.push(ref.ref);} + } + if(isInhibitGate(node.type)){ + for(const ref of node.type.condition){ + if(ref?.ref){targets.push(ref.ref);} } } - return targets; - }else{ - return []; } + return targets; + } /** - * Getter for the aspect of a FTA component. - * @param node AstNode which aspect should determined. - * @returns the aspect of {@code node}. + * Getter for the type of a FTA component. + * @param node AstNode which type should be determined. + * @returns the type of {@code node}. */ -export function getAspect(node: AstNode): FTAAspect { +export function getFTNodeType(node: AstNode): FTNodeType { if (isTopEvent(node)) { - return FTAAspect.TOPEVENT; + return FTNodeType.TOPEVENT; }else if (isComponent(node)) { - return FTAAspect.COMPONENT; + return FTNodeType.COMPONENT; }else if (isCondition(node)) { - return FTAAspect.CONDITION; + return FTNodeType.CONDITION; }else if(isGate(node) && isAND(node.type)){ - return FTAAspect.AND; + return FTNodeType.AND; }else if (isGate(node) && isOR(node.type)) { - return FTAAspect.OR; + return FTNodeType.OR; }else if (isGate(node) && isKNGate(node.type)) { - return FTAAspect.KN; + return FTNodeType.KN; }else if (isGate(node) && isInhibitGate(node.type)) { - return FTAAspect.INHIBIT; + return FTNodeType.INHIBIT; } - return FTAAspect.UNDEFINED; + return FTNodeType.UNDEFINED; } /** Sorts every gate with its type and puts them into a two dimensional array - * @param everyGate Every gate within the FTAModel + * @param gates Every gate within the FTAModel * @returns A two dimensional array with every gate sorted into the respective category of And, Or, KN, Inhibit-Gate */ -export function getAllGateTypes(everyGate: Gate[]): AstNode[][]{ +export function getAllGateTypes(gates: Gate[]): Map{ + const allGates: Map = new Map(); + const andGates: AstNode[] = []; const orGates: AstNode[] = []; const kNGates: AstNode[] = []; const inhibGates: AstNode[] = []; - for(const gate of everyGate){ - if(gate.type.$type === 'AND'){ + for(const gate of gates){ + if(isAND(gate.type)){ andGates.push(gate); - }else if(gate.type.$type === 'OR'){ + }else if(isOR(gate.type)){ orGates.push(gate); - }else if(gate.type.$type === 'KNGate'){ + }else if(isKNGate(gate.type)){ kNGates.push(gate); - }else if(gate.type.$type === 'InhibitGate'){ + }else if(isInhibitGate(gate.type)){ inhibGates.push(gate); } } - const result: AstNode[][] = [andGates, orGates, kNGates, inhibGates]; - return result; -} -/** - * Sets the level property for {@code nodes} depending on the layer they should be in. - * @param nodes The nodes representing the stpa components. - */ -export function setLevelsForFTANodes(nodes: FTANode[], edges: FTAEdge[]): void{ - //start with the top event - const topevent: FTANode[] = [getTopEvent(nodes)]; - determineLevelForChildren(topevent, 0, edges, nodes); -} - -/** - * Returns the top event. - * @param nodes All nodes. - * @returns the top event from all nodes. - */ -function getTopEvent(nodes: FTANode[]): FTANode{ - for(const node of nodes){ - if(node.aspect === FTAAspect.TOPEVENT){ - return node; - } - } - const empty = {} as FTANode; - return empty; + allGates.set('AND', andGates); + allGates.set('OR', orGates); + allGates.set('KNGate', kNGates); + allGates.set('InhibitGate', inhibGates); + return allGates; } /** - * Recursively determine the level of all nodes, starting with the top event. - * @param nodes All the nodes on the current layer we want to look at. At the start, this is just the top event. - * @param level The current level we want to assign. - * @param edges All edges in the graph. - * @param allNodes All nodes in the graph. - */ -function determineLevelForChildren(nodes: FTANode[], level: number, edges: FTAEdge[], allNodes: FTANode[]): void{ - const children: FTANode[] = []; - - for(const node of nodes){ - //for every node on current layer assign the level. - node.level = level; - for(const edge of edges){ - //Look at every edge that starts from our current node. - if(edge.sourceId === node.id){ - //Get child that is connected to the second part of the edge - const child = getChildWithID(allNodes, edge.targetId); //Edge from G1 to G3 (G1:-:G3) with G3 being the targetId. - if(!children.some((c) => c.id === child.id) && child.aspect !== FTAAspect.CONDITION){ //Don't add conditions yet. - children.push(getChildWithID(allNodes, edge.targetId)); - } + * Takes all (minimal) cut sets and returns a string that resembles it, so it can be displayed in the console. + * @param cutSets The (minimal) cut sets of the current Fault Tree. + * @returns A string that resembles the cut sets. + */ +export function CutSetToString(cutSets:FTANode[][]):string{ + let result = "["; + + for(const set of cutSets){ + result += "["; + for(const element of set){ + if(set.indexOf(element) === set.length -1){ + result += element.id; + }else{ + result = result + element.id + ","; } } - } - //If there is an inhibit gate in the next iteration/layer, then also - //add Condition to children, so that they can be on the same layer as the inhibit gates. - for(const node of children){ - if(node.aspect === FTAAspect.INHIBIT){ - for(const edge of edges){ - if(edge.sourceId === node.id){ - node.level = level; - const child = getChildWithID(allNodes, edge.targetId); - if(child.aspect === FTAAspect.CONDITION){ - children.push(child); - } - } - } + result += "]"; + if(cutSets.indexOf(set) === cutSets.length -1){ + result += "] \n"; + }else{ + result += ", \n"; } } - level++; - //Only repeat until there is no layer below - if(children.length !== 0){ - determineLevelForChildren(children, level, edges, allNodes); - } - -} - - -/** - * Gets a child object with its id from all nodes. - * @param nodes All FtaNodes we want to assign levels to. - * @param id The id of Node. - * @returns an FTANode with the given id. - */ -function getChildWithID(nodes: FTANode[], id: String): FTANode{ - for(const node of nodes){ - if(node.id === id){ - return node; - } - } - const empty = {} as FTANode; - return empty; + return result; } diff --git a/extension/src-language-server/fta/fta-validator.ts b/extension/src-language-server/fta/fta-validator.ts index d31c974..d68276f 100644 --- a/extension/src-language-server/fta/fta-validator.ts +++ b/extension/src-language-server/fta/fta-validator.ts @@ -1,3 +1,19 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ import { ValidationAcceptor, ValidationChecks, ValidationRegistry } from 'langium'; import { ModelFTA, StpaAstType } from '../generated/ast'; @@ -28,11 +44,15 @@ export class FtaValidator { * @param accept */ checkModel(model: ModelFTA, accept: ValidationAcceptor): void{ - this.checkUniqueComponents(model, accept); - this.checkUniqueGates(model, accept); + this.checkUniqueIdentifiers(model, accept); } - //prevent multiple components from having the same identifier. - checkUniqueComponents(model: ModelFTA, accept: ValidationAcceptor): void{ + + /** + * Prevent multiple components and gates from having the same identifier. + * @param model The model to validate. + * @param accept + */ + checkUniqueIdentifiers(model: ModelFTA, accept: ValidationAcceptor): void{ const componentNames = new Set(); model.components.forEach(c => { if (componentNames.has(c.name)) { @@ -40,15 +60,15 @@ export class FtaValidator { } componentNames.add(c.name); }); - } - //prevent multiple gates from having the same identifier. - checkUniqueGates(model:ModelFTA, accept:ValidationAcceptor): void{ + const gateNames = new Set(); model.gates.forEach(g => { - if (gateNames.has(g.name)) { + if (gateNames.has(g.name) || componentNames.has(g.name)) { accept('error', `Gate has non-unique name '${g.name}'.`, {node: g, property: 'name'}); } gateNames.add(g.name); }); + } + } diff --git a/extension/src-language-server/handler.ts b/extension/src-language-server/handler.ts index 41441d2..1402894 100644 --- a/extension/src-language-server/handler.ts +++ b/extension/src-language-server/handler.ts @@ -31,7 +31,6 @@ export function addNotificationHandler(connection: Connection, shared: LangiumSp connection.onNotification('diagram/selected', (msg: {label: string, uri: string}) => { // get the current model const model = getModel(msg.uri, shared); - // const modelFta = getModel(msg.uri, shared); // determine the range in the editor of the component identified by "label" const range = getRangeOfNode(model, msg.label); diff --git a/extension/src-language-server/main.ts b/extension/src-language-server/main.ts index ef48d23..21bef5c 100644 --- a/extension/src-language-server/main.ts +++ b/extension/src-language-server/main.ts @@ -35,7 +35,7 @@ startLanguageServer(shared); addDiagramHandler(connection, shared); addSTPANotificationHandler(connection, stpa, shared); -addFTANotificationHandler(connection, fta, shared); +addFTANotificationHandler(connection, fta); addNotificationHandler(connection, shared); // handle configuration changes for the validation checks diff --git a/extension/src-language-server/options/actions.ts b/extension/src-language-server/options/actions.ts index 6ecaa57..7cbc2b3 100644 --- a/extension/src-language-server/options/actions.ts +++ b/extension/src-language-server/options/actions.ts @@ -21,7 +21,7 @@ import { SynthesisOption, ValuedSynthesisOption } from "./option-models"; /** Request message from the server to update the diagram options widget on the client. */ export interface UpdateOptionsAction extends Action { kind: typeof UpdateOptionsAction.KIND - valuedSynthesisOptions: ValuedSynthesisOption[] + valuedSynthesisOptions?: ValuedSynthesisOption[] clientId: string } @@ -29,13 +29,13 @@ export namespace UpdateOptionsAction { export const KIND = "updateOptions"; export function create( - valuedSynthesisOptions: ValuedSynthesisOption[], clientId: string, + valuedSynthesisOptions?: ValuedSynthesisOption[], ): UpdateOptionsAction { return { kind: KIND, - valuedSynthesisOptions, clientId, + valuedSynthesisOptions, }; } diff --git a/extension/src-language-server/stpa/stpa-diagramServer.ts b/extension/src-language-server/stpa/stpa-diagramServer.ts index 8bbfa40..8f494fa 100644 --- a/extension/src-language-server/stpa/stpa-diagramServer.ts +++ b/extension/src-language-server/stpa/stpa-diagramServer.ts @@ -27,10 +27,12 @@ export class StpaDiagramServer extends DiagramServer { clientId: string; constructor(dispatch: (action: A) => Promise, - services: DiagramServices, synthesisOptions: StpaSynthesisOptions, clientId: string) { + services: DiagramServices, clientId: string, synthesisOptions?: StpaSynthesisOptions) { super(dispatch, services); - this.stpaOptions = synthesisOptions; this.clientId = clientId; + if(synthesisOptions){ + this.stpaOptions = synthesisOptions; + } } accept(action: Action): Promise { @@ -84,7 +86,11 @@ export class StpaDiagramServer extends DiagramServer { this.state.currentRoot = newRoot; await this.submitModel(this.state.currentRoot, true, action); // ensures the the filterUCA option is correct - this.dispatch({ kind: UpdateOptionsAction.KIND, valuedSynthesisOptions: this.stpaOptions.getSynthesisOptions(), clientId: this.clientId }); + if(this.stpaOptions){ + this.dispatch({ kind: UpdateOptionsAction.KIND, clientId: this.clientId, valuedSynthesisOptions: this.stpaOptions.getSynthesisOptions()}); + }else{ + this.dispatch({ kind: UpdateOptionsAction.KIND, clientId: this.clientId}); + } } catch (err) { this.rejectRemoteRequest(action, err as Error); console.error('Failed to generate diagram:', err); @@ -93,7 +99,11 @@ export class StpaDiagramServer extends DiagramServer { protected async handleRequestModel(action: RequestModelAction): Promise { await super.handleRequestModel(action); - this.dispatch({ kind: UpdateOptionsAction.KIND, valuedSynthesisOptions: this.stpaOptions.getSynthesisOptions(), clientId: this.clientId }); + if(this.stpaOptions){ + this.dispatch({ kind: UpdateOptionsAction.KIND, clientId: this.clientId, valuedSynthesisOptions: this.stpaOptions.getSynthesisOptions()}); + }else{ + this.dispatch({ kind: UpdateOptionsAction.KIND, clientId: this.clientId}); + } } } \ No newline at end of file diff --git a/extension/src-language-server/stpa/stpa-module.ts b/extension/src-language-server/stpa/stpa-module.ts index 26addd2..387bb63 100644 --- a/extension/src-language-server/stpa/stpa-module.ts +++ b/extension/src-language-server/stpa/stpa-module.ts @@ -113,7 +113,7 @@ export const stpaDiagramServerFactory = } return new StpaDiagramServer(async action => { connection?.sendNotification(DiagramActionNotification.type, { clientId, action }); - }, language.diagram, language.options.StpaSynthesisOptions, clientId); + }, language.diagram, clientId, language.options.StpaSynthesisOptions); }; }; diff --git a/extension/src-language-server/utils.ts b/extension/src-language-server/utils.ts index 4d3b1ef..d350eb0 100644 --- a/extension/src-language-server/utils.ts +++ b/extension/src-language-server/utils.ts @@ -30,12 +30,4 @@ export function getModel(uri: string, shared: LangiumSprottySharedServices): Mod const textDocuments = shared.workspace.LangiumDocuments; const currentDoc = textDocuments.getOrCreateDocument(URI.parse(uri)) as LangiumDocument; return currentDoc.parseResult.value; -} - -/* -export function getModelFta(uri: string, shared: LangiumSprottySharedServices): ModelFTA { - const textDocuments = shared.workspace.LangiumDocuments; - const currentDoc = textDocuments.getOrCreateDocument(URI.parse(uri)) as LangiumDocument; - return currentDoc.parseResult.value; -} -*/ \ No newline at end of file +} \ No newline at end of file diff --git a/extension/src-webview/css/fta-diagram.css b/extension/src-webview/css/fta-diagram.css index ea3a119..fde2b84 100644 --- a/extension/src-webview/css/fta-diagram.css +++ b/extension/src-webview/css/fta-diagram.css @@ -1,329 +1,186 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + @import "./fta-theme.css"; - - -.vscode-high-contrast .fta-node[aspect="0"], -.vscode-high-contrast .fta-edge-arrow[aspect="0"] { - fill: #AD0000; -} - -.vscode-high-contrast .fta-node[aspect="1"], -.vscode-high-contrast .fta-edge-arrow[aspect="1"] { - fill: #A85400; -} - -.vscode-high-contrast .fta-node[aspect="2"], -.vscode-high-contrast .fta-edge-arrow[aspect="2"] { - fill: #006600; -} - -.vscode-high-contrast .fta-node[aspect="3"], -.vscode-high-contrast .fta-edge-arrow[aspect="3"] { - fill: purple; -} - -.vscode-high-contrast .fta-node[aspect="4"], -.vscode-high-contrast .fta-edge-arrow[aspect="4"] { - fill: #0054A8; -} - -.vscode-high-contrast .fta-node[aspect="5"], -.vscode-high-contrast .fta-edge-arrow[aspect="5"] { - fill: #006161; -} - -.vscode-high-contrast .fta-node[aspect="6"], -.vscode-high-contrast .fta-edge-arrow[aspect="6"] { - fill: #616100; -} - - - -.vscode-high-contrast .fta-edge[aspect="0"], -.vscode-high-contrast .fta-edge-arrow[aspect="0"] { - stroke: #AD0000; -} - -.vscode-high-contrast .fta-edge[aspect="1"], -.vscode-high-contrast .fta-edge-arrow[aspect="1"] { - stroke: #A85400; -} - -.vscode-high-contrast .fta-edge[aspect="2"], -.vscode-high-contrast .fta-edge-arrow[aspect="2"] { - stroke: #006600; -} - -.vscode-high-contrast .fta-edge[aspect="3"], -.vscode-high-contrast .fta-edge-arrow[aspect="3"] { - stroke: purple; -} - -.vscode-high-contrast .fta-edge[aspect="4"], -.vscode-high-contrast .fta-edge-arrow[aspect="4"] { - stroke: #0054A8; -} - -.vscode-high-contrast .fta-edge[aspect="5"], -.vscode-high-contrast .fta-edge-arrow[aspect="5"] { - stroke: #006161; -} - -.vscode-high-contrast .fta-edge[aspect="6"], -.vscode-high-contrast .fta-edge-arrow[aspect="6"] { - stroke: #616100; -} - -.vscode-high-contrast .fta-edge[aspect="7"], -.vscode-high-contrast .fta-edge-arrow[aspect="7"] { - stroke: #2f4f4f; -} - - -.vscode-high-contrast .print-node { - fill: black; - stroke: white; -} - -.vscode-high-contrast .sprotty-node { - fill: #0c598d; - stroke: black; -} - - .vscode-dark .fta-node[aspect="0"] { fill: var(--fta-dark-node); - stroke: var(--fta-topevent-dark); + stroke: var(--fta-light-node); stroke-width: 2; } .vscode-dark .fta-node[aspect="1"] { fill: var(--fta-dark-node); - stroke: var(--fta-component-dark); + stroke: var(--fta-light-node); stroke-width: 2; } .vscode-dark .fta-node[aspect="2"] { fill: var(--fta-dark-node); - stroke: var(--fta-condition-dark); + stroke: var(--fta-light-node); stroke-width: 2; } .vscode-dark .fta-node[aspect="3"] { fill: var(--fta-dark-node); - stroke: var(--fta-and-dark); + stroke: var(--fta-light-node); stroke-width: 2; } .vscode-dark .fta-node[aspect="4"] { fill: var(--fta-dark-node); - stroke: var(--fta-or-dark); + stroke: var(--fta-light-node); stroke-width: 2; } .vscode-dark .fta-node[aspect="5"] { fill: var(--fta-dark-node); - stroke: var(--fta-kn-dark); + stroke: var(--fta-light-node); stroke-width: 2; } .vscode-dark .fta-node[aspect="6"] { fill: var(--fta-dark-node); - stroke: var(--fta-inhibit-dark); + stroke: var(--fta-light-node); stroke-width: 2; } -.vscode-dark .fta-edge-arrow[aspect="0"] { - fill: var(--fta-topevent-dark); -} - -.vscode-dark .fta-edge-arrow[aspect="1"] { - fill: var(--fta-component-dark); -} - -.vscode-dark .fta-edge-arrow[aspect="2"] { - fill: var(--fta-condition-dark); -} - -.vscode-dark .fta-edge-arrow[aspect="3"] { - fill: var(--fta-and-dark); -} - -.vscode-dark .fta-edge-arrow[aspect="4"] { - fill: var(--fta-or-dark); -} - -.vscode-dark .fta-edge-arrow[aspect="5"] { - fill: var(--fta-kn-dark); -} - -.vscode-dark .fta-edge-arrow[aspect="6"] { - fill: var(--fta-inhibit-dark); -} - .vscode-dark .fta-edge[aspect="0"], .vscode-dark .fta-edge-arrow[aspect="0"] { - stroke: var(--fta-topevent-dark); + stroke: var(--fta-light-node); } .vscode-dark .fta-edge[aspect="1"], .vscode-dark .fta-edge-arrow[aspect="1"] { - stroke: var(--fta-component-dark); + stroke: var(--fta-light-node); } .vscode-dark .fta-edge[aspect="2"], .vscode-dark .fta-edge-arrow[aspect="2"] { - stroke: var(--fta-condition-dark); + stroke: var(--fta-light-node); } .vscode-dark .fta-edge[aspect="3"], .vscode-dark .fta-edge-arrow[aspect="3"] { - stroke: var(--fta-and-dark); + stroke: var(--fta-light-node); } .vscode-dark .fta-edge[aspect="4"], .vscode-dark .fta-edge-arrow[aspect="4"] { - stroke: var(--fta-or-dark); + stroke: var(--fta-light-node); } .vscode-dark .fta-edge[aspect="5"], .vscode-dark .fta-edge-arrow[aspect="5"] { - stroke: var(--fta-or-dark); + stroke: var(--fta-light-node); } .vscode-dark .fta-edge[aspect="6"], .vscode-dark .fta-edge-arrow[aspect="6"] { - stroke: var(--fta-inhibit-dark); -} - -.vscode-dark .print-node { - fill: var(--fta-dark-node); stroke: var(--fta-light-node); - stroke-width: 2; } -.vscode-dark .sprotty-node { - fill: #0c598d; - stroke: black; -} .vscode-light .fta-node[aspect="0"] { fill: var(--fta-light-node); - stroke: var(--fta-topevent-light); + stroke: black; stroke-width: 2; } .vscode-light .fta-node[aspect="1"] { fill: var(--fta-light-node); - stroke: var(--fta-component-light); + stroke: black; stroke-width: 2; } .vscode-light .fta-node[aspect="2"] { fill: var(--fta-light-node); - stroke: var(--fta-condition-light); + stroke: black; stroke-width: 2; } .vscode-light .fta-node[aspect="3"] { fill: var(--fta-light-node); - stroke: var(--fta-and-light); + stroke: black; stroke-width: 2; } .vscode-light .fta-node[aspect="4"] { fill: var(--fta-light-node); - stroke: var(--fta-or-light); + stroke: black; stroke-width: 2; } .vscode-light .fta-node[aspect="5"] { fill: var(--fta-light-node); - stroke: var(--fta-kn-light); + stroke: black; stroke-width: 2; } .vscode-light .fta-node[aspect="6"] { fill: var(--fta-light-node); - stroke: var(--fta-inhibit-light); + stroke: black; stroke-width: 2; } -.vscode-light .fta-edge-arrow[aspect="0"] { - fill: var(--fta-topevent-light); -} - -.vscode-light .fta-edge-arrow[aspect="1"] { - fill: var(--fta-component-light); -} - -.vscode-light .fta-edge-arrow[aspect="2"] { - fill: var(--fta-condition-light); -} - -.vscode-light .fta-edge-arrow[aspect="3"] { - fill: var(--fta-and-light); -} - -.vscode-light .fta-edge-arrow[aspect="4"] { - fill: var(--fta-or-light); -} - -.vscode-light .fta-edge-arrow[aspect="5"] { - fill: var(--fta-kn-light); -} - -.vscode-light .fta-edge-arrow[aspect="6"] { - fill: var(--fta-inhibit-light); -} - .vscode-light .fta-edge[aspect="0"], .vscode-light .fta-edge-arrow[aspect="0"] { - stroke: var(--fta-topevent-light); + stroke: var(--fta-dark-node); + stroke-width: 2; } .vscode-light .fta-edge[aspect="1"], .vscode-light .fta-edge-arrow[aspect="1"] { - stroke: var(--fta-component-light); + stroke: var(--fta-dark-node); + stroke-width: 2; } .vscode-light .fta-edge[aspect="2"], .vscode-light .fta-edge-arrow[aspect="2"] { - stroke: var(--fta-condition-light); + stroke: var(--fta-dark-node); + stroke-width: 2; } .vscode-light .fta-edge[aspect="3"], .vscode-light .fta-edge-arrow[aspect="3"] { - stroke: var(--fta-and-light); + stroke: var(--fta-dark-node); + stroke-width: 2; } .vscode-light .fta-edge[aspect="4"], .vscode-light .fta-edge-arrow[aspect="4"] { - stroke: var(--fta-or-light); + stroke: var(--fta-dark-node); + stroke-width: 2; } .vscode-light .fta-edge[aspect="5"], .vscode-light .fta-edge-arrow[aspect="5"] { - stroke: var(--fta-kn-light); + stroke: var(--fta-dark-node); + stroke-width: 2; } .vscode-light .fta-edge[aspect="6"], .vscode-light .fta-edge-arrow[aspect="6"] { - stroke: var(--fta-inhibit-light); -} - -.vscode-light .print-node { - fill: var(--fta-light-node); - stroke: black; -} - -.vscode-light .sprotty-node { - fill: #50b1f1; - stroke: black; + stroke: var(--fta-dark-node); + stroke-width: 2; } .fta-text{ diff --git a/extension/src-webview/css/fta-theme.css b/extension/src-webview/css/fta-theme.css index e588271..cfba843 100644 --- a/extension/src-webview/css/fta-theme.css +++ b/extension/src-webview/css/fta-theme.css @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2022 by + * Copyright 2023 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts index 64dcf2c..803e3fc 100644 --- a/extension/src-webview/di.config.ts +++ b/extension/src-webview/di.config.ts @@ -31,9 +31,8 @@ import { sidebarModule } from './sidebar'; import { optionsModule } from './options/options-module'; import { StpaModelViewer} from './model-viewer'; import { StpaMouseListener } from './stpa-mouselistener'; -//import { FTANodeView, PolylineArrowEdgeViewFTA } from './fta-views'; -import { FTANode, FTA_EDGE_TYPE, FTA_NODE_TYPE} from './fta-model'; -import { FTANodeView, PolylineArrowEdgeViewFTA } from './fta-views'; +import { FTAEdge, FTANode, FTA_EDGE_TYPE, FTA_NODE_TYPE, TREE_TYPE} from './fta-model'; +import { FTAGraphView, FTANodeView, PolylineArrowEdgeViewFTA } from './fta-views'; const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); @@ -60,7 +59,8 @@ const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => configureModelElement(context, 'pre-rendered', PreRenderedElement, PreRenderedView); //FTA - configureModelElement(context, FTA_EDGE_TYPE, SEdge, PolylineArrowEdgeViewFTA); + configureModelElement(context, TREE_TYPE, SNode, FTAGraphView); + configureModelElement(context, FTA_EDGE_TYPE, FTAEdge, PolylineArrowEdgeViewFTA); configureModelElement(context, FTA_NODE_TYPE, FTANode, FTANodeView); }); diff --git a/extension/src-webview/fta-model.ts b/extension/src-webview/fta-model.ts index e85777d..092d2cd 100644 --- a/extension/src-webview/fta-model.ts +++ b/extension/src-webview/fta-model.ts @@ -1,10 +1,27 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + import { SEdge, SNode, connectableFeature, fadeFeature, hoverFeedbackFeature, layoutContainerFeature, popupFeature, selectFeature } from "sprotty"; + // The types of diagram elements export const FTA_NODE_TYPE = 'node:fta'; -export const PARENT_TYPE = 'node:parent'; -export const EDGE_TYPE = 'edge'; +export const TREE_TYPE = 'node:tree'; export const FTA_EDGE_TYPE = 'edge:fta'; @@ -16,17 +33,16 @@ export class FTANode extends SNode{ static readonly DEFAULT_FEATURES = [connectableFeature, selectFeature, layoutContainerFeature, fadeFeature, hoverFeedbackFeature, popupFeature]; - aspect: FTAAspect = FTAAspect.UNDEFINED; + nodeType: FTNodeType = FTNodeType.UNDEFINED; description: string = ""; highlight?: boolean; - level?: number; k?: number; n?: number; } /** - * Edge representing an edge in the relationship graph. + * Edge representing an edge in the fault tree. */ export class FTAEdge extends SEdge { highlight?: boolean; @@ -34,9 +50,9 @@ export class FTAEdge extends SEdge { /** - * The different aspects of FTA. + * The different types of nodes of FTA. */ -export enum FTAAspect { +export enum FTNodeType { TOPEVENT, COMPONENT, CONDITION, @@ -45,21 +61,4 @@ export enum FTAAspect { KN, INHIBIT, UNDEFINED -} - - -/** - * Node representing a system component in the BDD. - */ -export class BDDNode extends SNode { - level?: number; - static readonly DEFAULT_FEATURES = [connectableFeature, selectFeature, - layoutContainerFeature, fadeFeature, hoverFeedbackFeature, popupFeature]; -} - -/** - * Edge representing component failure in the BDD. - */ -export class BDDEdge extends SEdge { - fail?: boolean; } \ No newline at end of file diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index 4df2d12..749dddc 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -1,23 +1,33 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + /** @jsx svg */ import { inject, injectable } from 'inversify'; import { VNode } from "snabbdom"; -import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SEdge, SPort, svg } from 'sprotty'; +import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SEdge, SNode, svg } from 'sprotty'; import { DISymbol } from "./di.symbols"; -import { FTAAspect, FTAEdge, FTANode, FTA_EDGE_TYPE } from './fta-model'; -import { ColorStyleOption, RenderOptionsRegistry } from './options/render-options-registry'; -import { renderAndGate, renderCircle, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "./views-rendering"; +import { FTAEdge, FTANode, FTA_EDGE_TYPE, FTNodeType } from './fta-model'; import { CutSetsRegistry } from './options/cut-set-registry'; +import { renderAndGate, renderCircle, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "./views-rendering"; -/** Determines if path/aspect highlighting is currently on. */ -let highlightingFTA: boolean; - @injectable() export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { - @inject(DISymbol.RenderOptionsRegistry) renderOptionsRegistry: RenderOptionsRegistry; - @inject(DISymbol.CutSetsRegistry) cutSetsRegistry: CutSetsRegistry; - protected renderLine(edge: SEdge, segments: Point[], context: RenderingContext): VNode { const firstPoint = segments[0]; let path = `M ${firstPoint.x},${firstPoint.y}`; @@ -26,12 +36,16 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { path += ` L ${p.x},${p.y}`; } - highlightingFTA = this.cutSetsRegistry.getFtaHightlighting(); - // if an FTANode is selected, the components not connected to it should fade out - const hidden = edge.type == FTA_EDGE_TYPE && highlightingFTA && !(edge as FTAEdge).highlight; + if((edge.target as FTANode).highlight === true){ + (edge as FTAEdge).highlight = true; + }else{ + (edge as FTAEdge).highlight = false; + } - const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption); - return ; + // if an FTANode is selected, the components not connected to it should fade out + const hidden = edge.type == FTA_EDGE_TYPE && !(edge as FTAEdge).highlight; + + return ; } } @@ -40,37 +54,33 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { @injectable() export class FTANodeView extends RectangularNodeView { - @inject(DISymbol.RenderOptionsRegistry) renderOptionsRegistry: RenderOptionsRegistry; @inject(DISymbol.CutSetsRegistry) cutSetsRegistry: CutSetsRegistry; render(node: FTANode, context: RenderingContext): VNode { - const colorStyle = this.renderOptionsRegistry.getValue(ColorStyleOption); - const printNode = colorStyle == "black & white"; - const sprottyNode = colorStyle == "standard"; - const coloredNode = colorStyle == "colorful"; - // create the element based on the aspect of the node + + // create the element based on the type of the node let element: VNode; - switch (node.aspect) { - case FTAAspect.TOPEVENT: + switch (node.nodeType) { + case FTNodeType.TOPEVENT: element = renderRectangle(node); break; - case FTAAspect.COMPONENT: + case (FTNodeType.COMPONENT || FTNodeType.CONDITION): element = renderCircle(node); break; - case FTAAspect.CONDITION: + case FTNodeType.CONDITION: element = renderCircle(node); break; - case FTAAspect.AND: + case FTNodeType.AND: element = renderAndGate(node); break; - case FTAAspect.OR: + case FTNodeType.OR: element = renderOrGate(node); break; - case FTAAspect.KN: + case FTNodeType.KN: element = renderKnGate(node, node.k as number, node.n as number); break; - case FTAAspect.INHIBIT: + case FTNodeType.INHIBIT: element = renderInhibitGate(node); break; default: @@ -78,20 +88,71 @@ export class FTANodeView extends RectangularNodeView { break; } - highlightingFTA = this.cutSetsRegistry.getFtaHightlighting(); + //highlight every node that is in the selected cut set. + let set = this.cutSetsRegistry.getCurrentValue(); + if(set !== undefined){ + if(set === '-' ){ + node.highlight = true; + }else{ + node.highlight = false; + if(node.nodeType === FTNodeType.COMPONENT || node.nodeType === FTNodeType.CONDITION){ + if(set.includes(node.id)){ + node.highlight = true; + }else{ + node.highlight = false; + } + }else{ + if(this.checkIfHighlighted(node, set) === true){ + node.highlight = true; + }else{ + node.highlight = false; + } + } + } + } + //if an FTANode is selected, the components not connected to it should fade out - const hidden = highlightingFTA && !node.highlight; + const hidden = !node.highlight; return {element} {context.renderChildren(node)} ; } + + checkIfHighlighted(node: FTANode, set: any):boolean{ + for(const edge of node.outgoingEdges){ + let target = (edge.target as FTANode); + if((target.nodeType === FTNodeType.COMPONENT || target.nodeType === FTNodeType.CONDITION)){ + if(set.includes(target.id)){ + return true; + } + }else{ + if(this.checkIfHighlighted(target, set) === true){ + return true; + } + } + } + return false; + } +} + +@injectable() +export class FTAGraphView extends RectangularNodeView{ + + + render(node: SNode, context: RenderingContext): VNode { + + return + + {context.renderChildren(node)} + ; + } } \ No newline at end of file diff --git a/extension/src-webview/helper-methods.ts b/extension/src-webview/helper-methods.ts index 4b82135..26f24f3 100644 --- a/extension/src-webview/helper-methods.ts +++ b/extension/src-webview/helper-methods.ts @@ -17,12 +17,8 @@ import { SNode, SEdge, SModelElement } from "sprotty"; import { STPAAspect, STPAEdge, STPANode, STPA_NODE_TYPE } from "./stpa-model"; -import { FTAAspect, FTAEdge, FTANode } from "./fta-model"; -export const allFTANodes:FTANode[] = []; -export const allFTAEdges:FTAEdge[] = []; - /** * Collects all children of the nodes in {@code nodes}. * @param nodes The nodes, which children should be selected. @@ -185,70 +181,3 @@ export function flagSameAspect(selected: STPANode): STPANode[] { return elements; } -export function setFTANodesAndEdges(allNodes:SNode[], allEdges:SEdge[]):void{ - allNodes.forEach(node => { - if(node instanceof FTANode){ - allFTANodes.push(node); - } - }); - allEdges.forEach(edge => { - if(edge instanceof FTAEdge){ - allFTAEdges.push(edge); - } - }); -} - -export function flagHighlightedFta(highlightedCutSet: string[]):void{ - highlightCutSet(highlightedCutSet); - - let topEvent = {} as FTANode; - for(const node of allFTANodes){ - if(node.aspect === FTAAspect.TOPEVENT){ - topEvent = node; - } - } - - highlightPath(topEvent); - - -} -function highlightCutSet(highlightCutSet: string[]):void{ - for(const node of allFTANodes){ - if(highlightCutSet.includes(node.id)){ - node.highlight = true; - - }else{ - node.highlight = false; - } - } -} - - -function highlightPath(start:FTANode):boolean{ - - if(start.aspect === (FTAAspect.CONDITION || FTAAspect.COMPONENT)){ - if(start.highlight === true){ - return true; - }else{ - return false; - } - }else{ - //get all children of current node. - for(const edge of start.outgoingEdges){ - const child = edge.target as SNode; - const ftaNode = child as FTANode; - if(highlightPath(ftaNode) === true){ - start.highlight = true; - (edge as FTAEdge).highlight = true; - }else{ - (edge as FTAEdge).highlight = false; - } - } - - if(start.highlight === true){ - return true; - } - } - - return false; -} diff --git a/extension/src-webview/model-viewer.ts b/extension/src-webview/model-viewer.ts index fca42ce..ed35ff0 100644 --- a/extension/src-webview/model-viewer.ts +++ b/extension/src-webview/model-viewer.ts @@ -18,7 +18,6 @@ import { inject, postConstruct } from "inversify"; import { ModelViewer } from "sprotty"; import { DISymbol } from "./di.symbols"; -import { Model } from '../src-language-server/generated/ast'; export class StpaModelViewer extends ModelViewer { // @ts-ignore diff --git a/extension/src-webview/options/actions.ts b/extension/src-webview/options/actions.ts index 27ded11..945165b 100644 --- a/extension/src-webview/options/actions.ts +++ b/extension/src-webview/options/actions.ts @@ -132,13 +132,13 @@ export namespace SendConfigAction { export interface SendCutSetAction extends Action { kind: typeof SendCutSetAction.KIND; - cutSets: { id: string, value: any; }[]; + cutSets: { value: any; }[]; } export namespace SendCutSetAction { export const KIND = "sendCutSet"; - export function create(cutSets: { id: string, value: any; }[]): SendCutSetAction { + export function create(cutSets: { value: any; }[]): SendCutSetAction { return { kind: KIND, cutSets @@ -150,22 +150,4 @@ export interface SendCutSetAction extends Action { } } -export interface SelectCutSetAction extends Action { - kind: typeof SelectCutSetAction.KIND; - id: string; -} - - export namespace SelectCutSetAction { - export const KIND = "selectCutSet"; - export function create(id: string): SelectCutSetAction { - return { - kind: KIND, - id - }; - } - - export function isThisAction(action: Action): action is SelectCutSetAction { - return action.kind === SelectCutSetAction.KIND; - } -} \ No newline at end of file diff --git a/extension/src-webview/options/cut-set-panel.tsx b/extension/src-webview/options/cut-set-panel.tsx index 704799f..3b3c17f 100644 --- a/extension/src-webview/options/cut-set-panel.tsx +++ b/extension/src-webview/options/cut-set-panel.tsx @@ -1,3 +1,20 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + /** @jsx html */ import { SidebarPanel } from "../sidebar"; import { html } from "sprotty"; diff --git a/extension/src-webview/options/cut-set-registry.ts b/extension/src-webview/options/cut-set-registry.ts index 922d11e..5d4da6a 100644 --- a/extension/src-webview/options/cut-set-registry.ts +++ b/extension/src-webview/options/cut-set-registry.ts @@ -1,82 +1,92 @@ -import { inject, injectable } from "inversify"; +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { injectable } from "inversify"; +import { ICommand } from "sprotty"; import { Action, UpdateModelAction } from "sprotty-protocol"; import { Registry } from "../base/registry"; -import { VsCodeApi } from "sprotty-vscode-webview/lib/services"; -import { ICommand } from "sprotty"; -import { RenderOption, TransformationOptionType } from "./option-models"; -import { SelectCutSetAction, SendCutSetAction } from "./actions"; -import { flagHighlightedFta } from "../helper-methods"; +import { SendCutSetAction } from "./actions"; +import { DropDownOption, TransformationOptionType } from './option-models'; -export class DropDownMenuOption implements RenderOption{ +export class DropDownMenuOption implements DropDownOption{ static readonly ID: string = 'cut-sets'; static readonly NAME: string = 'Cut Sets'; readonly id: string = DropDownMenuOption.ID; readonly currentId:string = DropDownMenuOption.ID; readonly name: string = DropDownMenuOption.NAME; readonly type: TransformationOptionType = TransformationOptionType.DROPDOWN; + values: { displayName: string; id: string }[] = [{displayName: "---", id: "---"}]; availableValues: { displayName: string; id: string }[] = [{displayName: "---", id: "---"}]; readonly initialValue: { displayName: string; id: string } = {displayName: "---", id: "---"}; currentValue = {displayName: "---", id: "---"}; } -export interface RenderOptionType { - readonly ID: string, - readonly NAME: string, - new(): RenderOption, -} - - /** {@link Registry} that stores and updates different render options. */ @injectable() export class CutSetsRegistry extends Registry{ - private _options: Map = new Map(); - private selectedCutSet:string = ""; - private ftaHightlighting = false; - - @inject(VsCodeApi) private vscodeApi: VsCodeApi; + private _options: Map = new Map(); constructor(){ super(); } - handle(action: Action): void | Action | ICommand{ if(SendCutSetAction.isThisAction(action)){ const dropDownOption = new DropDownMenuOption(); - for(const entry of action.cutSets){ - dropDownOption.availableValues.push({displayName: entry.id , id: entry.id}); + for(const set of action.cutSets){ + let id = "["; + for(const element of set.value){ + if(set.value.indexOf(element) === set.value.length -1){ + id += element.id; + }else{ + id = id + element.id + ","; + } + } + id += "]"; + + dropDownOption.availableValues.push({displayName: id , id: id}); } this._options.set('cut-sets', dropDownOption); this.notifyListeners(); - }else if(SelectCutSetAction.isThisAction(action)){ - this.selectedCutSet = action.id; - this.ftaHightlighting = true; - this.highlightSelectedNodes(this.selectedCutSet); - } return UpdateModelAction.create([], { animate: false, cause: action }); } - get allOptions():RenderOption[]{ + get allOptions():DropDownOption[]{ return Array.from(this._options.values()); } - - highlightSelectedNodes(selectedCutSet:string):void{ - const selectedSet = selectedCutSet.slice(1,-1); // remove the brackets [] - if(selectedSet === '-'){ - this.ftaHightlighting = false; - } - - const componentsToHighlight = selectedSet.split(","); - flagHighlightedFta(componentsToHighlight); - } - getFtaHightlighting():boolean{ - return this.ftaHightlighting; + getCurrentValue():any{ + if(this._options.get('cut-sets')?.availableValues.length === 1){ + return undefined; + } + const selectedCutSet:{ displayName: string; id: string } = this._options.get('cut-sets')?.currentValue; + if(selectedCutSet){ + const selected = selectedCutSet.displayName.slice(1,-1); + if(selected === '-'){ + return '-'; + }else{ + return selected.split(","); + } + } } } \ No newline at end of file diff --git a/extension/src-webview/options/options-module.ts b/extension/src-webview/options/options-module.ts index 431240b..88b1719 100644 --- a/extension/src-webview/options/options-module.ts +++ b/extension/src-webview/options/options-module.ts @@ -18,14 +18,14 @@ import { ContainerModule } from "inversify"; import { configureActionHandler, TYPES } from "sprotty"; import { DISymbol } from "../di.symbols"; -import { SetRenderOptionAction, ResetRenderOptionsAction, SendConfigAction, SendCutSetAction, SelectCutSetAction } from "./actions"; -import { OptionsRenderer } from "./options-renderer"; -import { GeneralPanel } from "./general-panel"; -import { RenderOptionsRegistry } from "./render-options-registry"; -import { OptionsRegistry } from "./options-registry"; -import { OptionsPanel } from "./options-panel"; +import { ResetRenderOptionsAction, SendConfigAction, SendCutSetAction, SetRenderOptionAction } from "./actions"; import { CutSetPanel } from "./cut-set-panel"; import { CutSetsRegistry } from "./cut-set-registry"; +import { GeneralPanel } from "./general-panel"; +import { OptionsPanel } from "./options-panel"; +import { OptionsRegistry } from "./options-registry"; +import { OptionsRenderer } from "./options-renderer"; +import { RenderOptionsRegistry } from "./render-options-registry"; // import { VsCodeApi } from "sprotty-vscode-webview/lib/services"; /** Module that configures option related panels and registries. */ @@ -54,5 +54,4 @@ export const optionsModule = new ContainerModule((bind, _, isBound) => { configureActionHandler(ctx, ResetRenderOptionsAction.KIND, DISymbol.RenderOptionsRegistry); configureActionHandler(ctx, SendConfigAction.KIND, DISymbol.RenderOptionsRegistry); configureActionHandler(ctx, SendCutSetAction.KIND, DISymbol.CutSetsRegistry); - configureActionHandler(ctx, SelectCutSetAction.KIND, DISymbol.CutSetsRegistry); }); diff --git a/extension/src-webview/options/options-renderer.tsx b/extension/src-webview/options/options-renderer.tsx index 6788f0c..f53194e 100644 --- a/extension/src-webview/options/options-renderer.tsx +++ b/extension/src-webview/options/options-renderer.tsx @@ -18,10 +18,11 @@ /** @jsx html */ import { inject, injectable } from "inversify"; import { VNode } from "snabbdom"; -import { html, IActionDispatcher, TYPES } from "sprotty"; // eslint-disable-line @typescript-eslint/no-unused-vars +import { IActionDispatcher, TYPES, html } from "sprotty"; // eslint-disable-line @typescript-eslint/no-unused-vars +import { UpdateModelAction } from 'sprotty-protocol'; import { SetRenderOptionAction, - SetSynthesisOptionsAction, + SetSynthesisOptionsAction } from "./actions"; import { CategoryOption, @@ -29,11 +30,11 @@ import { } from "./components/option-inputs"; import { ChoiceRenderOption, - RenderOption, + DropDownOption, RangeOption as RangeOptionData, + RenderOption, SynthesisOption, - TransformationOptionType, - DropDownOption + TransformationOptionType } from "./option-models"; interface AllOptions { @@ -204,7 +205,7 @@ export class OptionsRenderer { value={option.currentValue} availableValues={(option as DropDownOption).availableValues} description={option.description} - onChange={this.handleRenderOptionChange.bind(this, option)} + onChange={this.setCurrentValueOnChange.bind(this,option)} /> ); default: @@ -217,4 +218,9 @@ export class OptionsRenderer { private handleRenderOptionChange(option: RenderOption, newValue: any) { this.actionDispatcher.dispatch(SetRenderOptionAction.create(option.id, newValue)); } + + private setCurrentValueOnChange(option:RenderOption, newValue:any){ + option.currentValue = {displayName: newValue, id: newValue} + this.actionDispatcher.dispatch(UpdateModelAction.create([], { animate: false })); + } } diff --git a/extension/src-webview/options/render-options-registry.ts b/extension/src-webview/options/render-options-registry.ts index 044a8f3..890e155 100644 --- a/extension/src-webview/options/render-options-registry.ts +++ b/extension/src-webview/options/render-options-registry.ts @@ -18,10 +18,10 @@ import { inject, injectable, postConstruct } from "inversify"; import { ICommand } from "sprotty"; import { Action, UpdateModelAction } from "sprotty-protocol"; +import { VsCodeApi } from "sprotty-vscode-webview/lib/services"; import { Registry } from "../base/registry"; -import { ResetRenderOptionsAction, SelectCutSetAction, SendConfigAction, SetRenderOptionAction } from "./actions"; +import { ResetRenderOptionsAction, SendConfigAction, SetRenderOptionAction } from "./actions"; import { ChoiceRenderOption, RenderOption, TransformationOptionType } from "./option-models"; -import { VsCodeApi } from "sprotty-vscode-webview/lib/services"; /** * Diffrent options for the color style of the relationship graph. @@ -116,13 +116,6 @@ export class RenderOptionsRegistry extends Registry { handle(action: Action): void | Action | ICommand { if (SetRenderOptionAction.isThisAction(action)) { const option = this._renderOptions.get(action.id); - - if(action.id === 'cut-sets'){ - const selectCutSetAction = {kind: SelectCutSetAction.KIND, id: action.value}; - this.vscodeApi.postMessage({action: selectCutSetAction}); - this.notifyListeners(); - return; - } if (!option) {return;} option.currentValue = action.value; diff --git a/extension/src-webview/views.tsx b/extension/src-webview/views.tsx index b202514..f38bd03 100644 --- a/extension/src-webview/views.tsx +++ b/extension/src-webview/views.tsx @@ -20,7 +20,7 @@ import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SEdge, SGraph, SGraphView, SNode, SPort, svg, toDegrees } from 'sprotty'; import { DISymbol } from './di.symbols'; -import { collectAllChildren, setFTANodesAndEdges } from './helper-methods'; +import { collectAllChildren } from './helper-methods'; import { ColorStyleOption, DifferentFormsOption, RenderOptionsRegistry, ShowCSOption, ShowRelationshipGraphOption } from './options/render-options-registry'; import { CS_EDGE_TYPE, CS_NODE_TYPE, PARENT_TYPE, STPAAspect, STPAEdge, STPANode, STPA_EDGE_TYPE, STPA_NODE_TYPE } from './stpa-model'; import { renderCircle, renderDiamond, renderHexagon, renderMirroredTriangle, renderPentagon, renderRectangle, renderRoundedRectangle, renderTrapez, renderTriangle } from './views-rendering'; @@ -199,8 +199,6 @@ export class STPAGraphView extends SGraphView { highlighting = allNodes.find(node => { return node instanceof STPANode && node.highlight }) !== undefined; - - setFTANodesAndEdges(allNodes, model.children as SEdge[]); return super.render(model, context, args); } diff --git a/extension/src/actions.ts b/extension/src/actions.ts index 5330c55..42434d7 100644 --- a/extension/src/actions.ts +++ b/extension/src/actions.ts @@ -63,13 +63,13 @@ export namespace SendConfigAction { export interface SendCutSetAction extends Action { kind: typeof SendCutSetAction.KIND; - cutSets: { id: string, value: any; }[]; + cutSets: { value: any; }[]; } export namespace SendCutSetAction { export const KIND = "sendCutSet"; - export function create(cutSets: { id: string, value: any; }[]): SendCutSetAction { + export function create(cutSets: { value: any; }[]): SendCutSetAction { return { kind: KIND, cutSets @@ -80,23 +80,3 @@ export interface SendCutSetAction extends Action { return action.kind === SendCutSetAction.KIND; } } - -export interface SelectCutSetAction extends Action { - kind: typeof SelectCutSetAction.KIND; - id: string; -} - - export namespace SelectCutSetAction { - export const KIND = "selectCutSet"; - - export function create(id: string): SelectCutSetAction { - return { - kind: KIND, - id - }; - } - - export function isThisAction(action: Action): action is SelectCutSetAction { - return action.kind === SelectCutSetAction.KIND; - } -} \ No newline at end of file diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index c09c6ff..5e53a1c 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -23,10 +23,12 @@ import { SprottyWebview } from 'sprotty-vscode/lib/sprotty-webview'; import * as vscode from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'; import { FTANode } from '../src-language-server/fta/fta-interfaces'; +import { CutSetToString } from '../src-language-server/fta/fta-utils'; import { SendCutSetAction, UpdateViewAction } from './actions'; import { ContextTablePanel } from './context-table-panel'; import { StpaFormattingEditProvider } from './stpa-formatter'; import { StpaLspWebview } from './wview'; +import { AstNode } from 'langium'; export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { @@ -174,27 +176,27 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { vscode.commands.executeCommand("redo"); }) ); + //commands for computing and displaying the (minimal) cut sets of the fault tree. this.context.subscriptions.push( - vscode.commands.registerCommand(this.extensionPrefix + '.generate.cutSets', async (...commandArgs: any[]) =>{ - this.lastUri = (commandArgs[0] as vscode.Uri).toString(); - const cutSets:FTANode[][] = await this.languageClient.sendRequest('generate/getCutSets', this.lastUri); + vscode.commands.registerCommand(this.extensionPrefix + '.generate.ftaCutSets', async () =>{ + const cutSets:AstNode[][] = await this.languageClient.sendRequest('generate/getCutSets'); - this.dispatchCutSetsToWebview(cutSets); + //Send cut sets to webview to display them in a dropdown menu. + //this.dispatchCutSetsToWebview(cutSets); const outputCutSets = vscode.window.createOutputChannel("All cut sets"); - outputCutSets.append("The resulting " + cutSets.length + " cut sets are: \n" + this.CutSetToString(cutSets)); + outputCutSets.append("The resulting " + cutSets.length + " cut sets are: \n" /* + CutSetToString(cutSets) */); outputCutSets.show(); }) ); this.context.subscriptions.push( - vscode.commands.registerCommand(this.extensionPrefix + '.generate.minimalCutSets', async (...commandArgs: any[]) =>{ - this.lastUri = (commandArgs[0] as vscode.Uri).toString(); - const minimalCutSets:FTANode[][] = await this.languageClient.sendRequest('generate/getMinimalCutSets', this.lastUri); + vscode.commands.registerCommand(this.extensionPrefix + '.generate.ftaMinimalCutSets', async () =>{ + const minimalCutSets:AstNode[][] = await this.languageClient.sendRequest('generate/getMinimalCutSets'); - this.dispatchCutSetsToWebview(minimalCutSets); + //this.dispatchCutSetsToWebview(minimalCutSets); const outputMinimalCutSets = vscode.window.createOutputChannel("All minimal cut sets"); - outputMinimalCutSets.append("The resulting " + minimalCutSets.length + " minimal cut sets are: \n" + this.CutSetToString(minimalCutSets)); + outputMinimalCutSets.append("The resulting " + minimalCutSets.length + " minimal cut sets are: \n" /* + CutSetToString(minimalCutSets) */); outputMinimalCutSets.show(); }) ); @@ -204,49 +206,14 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { * @param cutSets The (minimal) cut sets of the current Fault Tree. */ protected dispatchCutSetsToWebview(cutSets:FTANode[][]):void{ - const cutSetDropDownList: { id: string, value: any; }[] = []; + const cutSetDropDownList: { value: any; }[] = []; for(const set of cutSets){ - let id = "["; - for(const element of set){ - if(set.indexOf(element) === set.length -1){ - id += element.id; - }else{ - id = id + element.id + ","; - } - } - id += "]"; - cutSetDropDownList.push({id: id, value: set}); + cutSetDropDownList.push({value: set}); } this.singleton?.dispatch({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); } - /** - * Takes all (minimal) cut sets and returns a string that resembles it, so it can be displayed in the console. - * @param cutSets The (minimal) cut sets of the current Fault Tree. - * @returns A string that resembles the cut sets. - */ - protected CutSetToString(cutSets:FTANode[][]):string{ - let result = "["; - - for(const set of cutSets){ - result += "["; - for(const element of set){ - if(set.indexOf(element) === set.length -1){ - result += element.id; - }else{ - result = result + element.id + ","; - } - } - result += "]"; - if(cutSets.indexOf(set) === cutSets.length -1){ - result += "] \n"; - }else{ - result += ", \n"; - } - } - - return result; - } + /** * Creates a quickpick containing the values "true" and "false". The selected value is set for the diff --git a/extension/src/wview.ts b/extension/src/wview.ts index 24157c1..daa3ab7 100644 --- a/extension/src/wview.ts +++ b/extension/src/wview.ts @@ -18,7 +18,7 @@ import { hasOwnProperty, isActionMessage, SelectAction } from 'sprotty-protocol'; import { SprottyLspWebview } from "sprotty-vscode/lib/lsp"; import * as vscode from 'vscode'; -import { SelectCutSetAction, SendConfigAction } from './actions'; +import { SendConfigAction } from './actions'; export class StpaLspWebview extends SprottyLspWebview { @@ -36,9 +36,7 @@ export class StpaLspWebview extends SprottyLspWebview { case SelectAction.KIND: this.handleSelectAction(message.action as SelectAction); break; - case SelectCutSetAction.KIND: - this.selectCutSet(message.action as SelectCutSetAction); - break; + } } return super.receiveFromWebview(message); @@ -76,10 +74,6 @@ export class StpaLspWebview extends SprottyLspWebview { action.options.forEach(element => configOptions.update(element.id, element.value)); } - protected selectCutSet(action: SelectCutSetAction):void{ - this.dispatch(action); - } - } interface RenderOptionsRegistryReadyMessage { diff --git a/test.fta b/test.fta deleted file mode 100644 index 065be12..0000000 --- a/test.fta +++ /dev/null @@ -1,39 +0,0 @@ -Components -M1 "Redundant memory unit 1" -M2 "Redundant memory unit 2" -M3 "Redundant memory unit 3" -C1 "CPU1" -C2 "CPU2" -PS "Power supply" -B "System bus" - - -Conditions -U "In Use" - -TopEvent -"System Failure" = G1 - -Gates -G1 = U inhibits G2 -G2 = G3 or B -G3 = G4 and G5 -G4 = C1 or PS or G6 -G5 = C2 or PS or G6 -G6 = 2 of M1, M2, M3 - - -/* Gates -G1 = 3 of M1, G2, G3, C2 -G2 = U inhibits M2 -G3 = M3 and G4 -G4 = B or PS or C1 */ - - -/* Gates -G1 = G2 or B -G2 = G3 and G4 -G3 = C1 or G5 -G4 = C2 or G6 -G5 = 2 of M1, M2, M3 -G6 = 2 of S, T, K */ \ No newline at end of file diff --git a/teststpa.stpa b/teststpa.stpa deleted file mode 100644 index 52e977f..0000000 --- a/teststpa.stpa +++ /dev/null @@ -1,84 +0,0 @@ -Losses -L1 "Loss of life or serious injury to people" -L2 "Damage to the aircraft or objects outside the aircraft" - -Hazards -H1 "Loss of aircraft control" [L1, L2] -H2 "Aircraft comes too close to other objects" [L1, L2] { - H2.1 "Deceleration is insufficient" - "Acceleration" - H2.2 "Asymmetric acceleration maneuvers aircraft toward other objects" - H2.3 "Excessive acceleration provided while taxiing" -} - -SystemConstraints -SC2 "Aircraft must have safe distance to other objects" [H2] { - SC2.1 "Deceleration must occur within TBD seconds of landing or rejected takeoff at a rate of at least TBD m/s2" [H2.1] - SC2.2 "Assymetric acceleration must not ..." [H2.2] -} - -ControlStructure -Aircraft { - FlightCrew { - hierarchyLevel 0 - processModel { - BCSUmode: [on, off] - } - controlActions { - [mc "Manual Controls" ]-> OtherSubsystems - [powerOff "Power Off BSCU", powerOn "Power On BSCU"] -> BSCU - [manual "Manual Braking"] -> Wheels - } - } - OtherSubsystems { - hierarchyLevel 1 - feedback { - [modes "Other system modes", states "states"] -> FlightCrew - } - } - BSCU { - hierarchyLevel 1 - controlActions { - [brake "Brake"] -> Wheels - } - feedback { - [mode "BSCU mode", faults "BSCU faults"] -> FlightCrew - } - } - Wheels { - hierarchyLevel 2 - feedback { - [speed "Wheel speed"] -> BSCU - } - } -} - -Responsibilities -BSCU { - R1 "Actuate brakes when requested" [SC2.1] - R2 "Pulse brakes in case of a skid" [SC2.2] -} -FlightCrew { - R3 "Manually brake in case of a malfunction" [SC2.1, SC2.2] -} - -UCAs -FlightCrew.powerOff { - notProviding { - UCA1 "Crew does not provide BSCU Power Off when abnormal WBS behavior occurs" [H2.1] - } - providing { - UCA2 "Crew provides BSCU Power Off when Anti-Skid functionality is needed and WBS is functioning normally" [H2.3] - } - tooEarly/Late {} - stoppedTooSoon {} -} - -ControllerConstraints -C1 "Crew must provide the BSCU Power Off control action during abnormal WBS behavior" [UCA1] -C2 "Crew must not provide the BSCU Power Off control action when Anti-Skid functionality is needed" [UCA2] - -LossScenarios -Scenario1 for UCA1 "Abnormal WBS behavior occurs. Crew does not power off ..." [H2.1] -Scenario2 "Insufficient braking is applied due to ..." [H2.1] - From 32fe837f0c8318fd3ab5ab98eb55d9296bc65a88 Mon Sep 17 00:00:00 2001 From: Orhan Date: Wed, 12 Jul 2023 16:23:57 +0200 Subject: [PATCH 20/61] All Fixes --- .../fta/fta-cutSet-generator.ts | 397 ++++-------------- .../fta/fta-diagram-generator.ts | 32 +- .../fta/fta-message-handler.ts | 23 +- .../src-language-server/fta/fta-utils.ts | 45 +- .../src-webview/options/cut-set-registry.ts | 12 +- extension/src/language-extension.ts | 23 +- 6 files changed, 149 insertions(+), 383 deletions(-) diff --git a/extension/src-language-server/fta/fta-cutSet-generator.ts b/extension/src-language-server/fta/fta-cutSet-generator.ts index 06532bf..7e75004 100644 --- a/extension/src-language-server/fta/fta-cutSet-generator.ts +++ b/extension/src-language-server/fta/fta-cutSet-generator.ts @@ -16,21 +16,20 @@ */ import { AstNode } from 'langium'; +import { IdCache } from 'langium-sprotty'; import { isAND, isComponent, isCondition, isGate, isInhibitGate, isKNGate, isOR, isTopEvent } from '../generated/ast'; -import { FTAEdge, FTANode } from './fta-interfaces'; -import { FTNodeType } from "./fta-model"; export class CutSetGenerator{ /** - * Takes the Fault Tree and returns a two-dimensional array of FTANodes where every inner list resembles a minimal cut set. + * Takes the Fault Tree and returns a two-dimensional array of AstNodes where every inner list resembles a minimal cut set. * @param allNodes All Nodes in the graph. - * @param allEdges All Edges in the graph. + * @param idCache The idCache of the generator context from the current graph. * @returns A list of lists that that contains every minimal cut set of the given Fault Tree. */ - determineMinimalCutSet(allNodes:FTANode[], allEdges:FTAEdge[]):FTANode[][]{ - const bdd = this.generateCutSets(allNodes, allEdges); + determineMinimalCutSet(allNodes:AstNode[], idCache:IdCache):AstNode[][]{ + const bdd = this.generateCutSets(allNodes, idCache); //Cut sets are minimal if, when any basic event is removed from the set, the remaining events collectively are no longer a cut set. //Check every innerList @@ -48,7 +47,7 @@ export class CutSetGenerator{ * @param bdd All Cut Sets of the Fault Tree * @returns True if the given list is a minimal cut set or false if is not. */ - checkIfMinimalCutSet(innerList:FTANode[], bdd:FTANode[][]):boolean{ + checkIfMinimalCutSet(innerList:AstNode[], bdd:AstNode[][]):boolean{ for(const list of bdd){ if(list.every(e=>innerList.includes(e)) && innerList !== list){ return false; @@ -57,13 +56,14 @@ export class CutSetGenerator{ return true; } + /** - * Takes the Fault Tree and returns a two-dimensional array of FTANodes where every inner list resembles a cut set. + * Takes the Fault Tree and returns a two-dimensional array of AstNodes where every inner list resembles a cut set. * @param allNodes All Nodes in the graph. - * @param allEdges All Edges in the graph. + * @param idCache The idCache of the generator context from the current graph. * @returns A list of lists that that contains every cut set of the given Fault Tree. */ - generateCutSets(allNodes:FTANode[], allEdges:FTAEdge[]):FTANode[][]{ + generateCutSets(allNodes:AstNode[], idCache:IdCache):AstNode[][]{ //Idea: //Start from the top event. //Get the only child of top event (will always be only one) as our starting node. @@ -73,12 +73,12 @@ export class CutSetGenerator{ //When there is no gate, return the component - const startingNode = this.getChildOfTopEvent(allNodes, allEdges); - if(startingNode.nodeType === FTNodeType.COMPONENT){ + const startingNode = this.getChildOfTopEvent(allNodes); + if(isComponent(startingNode)){ return [[startingNode]]; } //Evaluate the child of the top event and recursively the entire Fault Tree. - const cutSets = this.evaluate(startingNode, allNodes, allEdges); + const cutSets = this.evaluate(startingNode, allNodes ,idCache); return cutSets; @@ -88,297 +88,23 @@ export class CutSetGenerator{ * Takes a single node and returns it evaluation depending on the node type and number of children. This function is called recursively for all children. * @param node The node we want to evaluate. * @param allNodes All Nodes in the graph. - * @param allEdges All Edges in the graph. + * @param idCache The idCache of the generator context from the current graph. * @returns A list of lists that is the result of evaluating the given node. */ - evaluate(node:FTANode, allNodes: FTANode[], allEdges: FTAEdge[]): FTANode[][]{ - let result:FTANode[][] = []; - - // we start with the top-most gate(child of topevent) and get all its children. - const children = this.getAllChildrenOfNode(node, allNodes, allEdges); - if(children.length === 0){return result;}; - - //if the node is an and/inhibit-gate we want to evaluate all children and concatenate all inner lists of one child with another. - if(node.nodeType === FTNodeType.AND || node.nodeType === FTNodeType.INHIBIT){ - for(const child of children){ - if(child.nodeType === FTNodeType.COMPONENT || child.nodeType === FTNodeType.CONDITION){ - result = this.concatAllLists([[child]], result); - }else{ - result = this.concatAllLists(this.evaluate(child, allNodes, allEdges), result); - } - } - //if the node is an or-gate we want to evaluate all children and add every single inner list to the result. - }else if(node.nodeType === FTNodeType.OR){ - for(const child of children){ - if(child.nodeType === FTNodeType.COMPONENT){ - const orList = [child]; - result.push(orList); - }else{ - for(const list of this.evaluate(child, allNodes, allEdges)){ //push every inner list of the child gate. - result.push(list); - } - } - } - - - //if the node is a kN-gate we want to get every combinations of the children with length k and after that evaluate the gates in the list. - }else if(node.nodeType === FTNodeType.KN){ - const k = node.k as number; - const n = node.n as number; - - //Example: With Children:[M1,M2,G1] and k=2 -> [[M1,M2],[M1,G1],[M2,G1]] . - const combinations:FTANode[][]=[]; - for(let i = k; i<=n; i++){ - for(const comb of this.getAllCombinations(children, i)){ - combinations.push(comb); - } - } - - //Now we want to evaluate G1 (e.g evaluation(G1) = [[C]]). - //Our result list should look like this -> [[M1,M2], [M1,C], [M2,C]]. - for(const comb of combinations){ - if(comb.some(e => e.nodeType === FTNodeType.AND || e.nodeType === FTNodeType.INHIBIT || e.nodeType === FTNodeType.OR || e.nodeType === FTNodeType.KN)){ - const evaluatedLists = this.evaluateGateInCombinationList(comb, allNodes, allEdges); - for(const list of evaluatedLists){ - result.push(list); - } - }else{ - result.push(comb); - } - } - } - - return result; - - } - - /** - * Takes a list of components, conditions and gates and then removes the gates and inserts its evaluation in the list. This can result in multiple lists. - * @param innerList The list we want to evaluate. - * @param allNodes All Nodes in the graph. - * @param allEdges All Edges in the graph. - * @returns A list of lists that is the result of inserting the evaluation of the gates in the given list. - */ - evaluateGateInCombinationList(innerList: FTANode[], allNodes:FTANode[], allEdges:FTAEdge[]):FTANode[][]{ - - let result:FTANode[][] = []; - const restList:FTANode[] = innerList; - - for(let i = 0; i nodes.length || k <= 0) { - return []; - } - if (k === nodes.length) { - return [nodes]; - } - if(k===1){ - for(let i = 0; i node.id === edge.targetId); - children.push(child as FTANode); - } - } - return children; - } - - - - /** - * Given all Nodes this method returns the first and only child of the topevent. - * @param nodes All FtaNodes in the graph. - * @param id All FTAEdges in the graph. - * @returns the child of the topevent. - */ - getChildOfTopEvent(allNodes:FTANode[], allEdges:FTAEdge[]): FTANode{ - const topEvent:FTANode = allNodes.find(node => node.nodeType === FTNodeType.TOPEVENT) as FTANode; - - for(const edge of allEdges){ - if(edge.sourceId === topEvent.id){ - return (allNodes.find(node => node.id === edge.targetId)) as FTANode; - } - } - - const empty = {} as FTANode; - return empty; - } - /** - * Concatenates every inner List of two two-dimensional arrays . - * @param a The first two-dimensional FTANode array. - * @param b The second two-dimensional FTANode array. - * @returns a two-dimensional array of type FTANode where every innerList of both arrays is concatenated. - */ - concatAllLists(a:FTANode[][], b:FTANode[][]):FTANode[][]{ - const result: FTANode[][] = []; - - if(a.length === 0){ - return b; - } - if(b.length === 0){ - return a; - } - - for (const innerA of a) { - for (const innerB of b) { - //Add only unique sets - let newSet = innerA.concat(innerB); - newSet = newSet.filter((e,i) => newSet.indexOf(e) === i); - if(this.indexOfArray(newSet, result) === -1){ - result.push(newSet); - } - - } - } - - return result; - - } - /** - * Checks if array a and b are equal, - * @param a The first array we want to compare. - * @param b The second array we want to compaare. - * @returns True if they are equal and false if not. - */ - arrayEquals(a:FTANode[], b:FTANode[]):boolean{ - const sortedA = a.sort((x,y) => (x.id > y.id ? -1 : 1)); - const sortedB = b.sort((x,y) => (x.id > y.id ? -1 : 1)); - return a.length === b.length && sortedA.every((e,i) => e === sortedB[i]); - } - - /** - * Gets the index of a list in a two-dimensional list of FTANodes, -1 otherwise. - * @param a The list we want the index of. - * @param b The two-dimensional list of FTANodes we want to search in. - * @returns the index of the list. - */ - indexOfArray(a:FTANode[], b:FTANode[][]):number{ - let i = 0; - for(const list of b){ - if(this.arrayEquals(a, list)){ - break; - } - i++; - } - if(i >= b.length){ - return -1; - } - return i; - } - -//---------------------------- - - determineMinimalCutSetAst(allNodes:AstNode[]):AstNode[][]{ - const bdd = this.generateCutSetsAst(allNodes); - - //Cut sets are minimal if, when any basic event is removed from the set, the remaining events collectively are no longer a cut set. - //Check every innerList - //If inner list contains another array from the bdd array, remove innerList because it cant be a minimal cut set - const minimalCutSet = bdd.filter(innerList => { - return this.checkIfMinimalCutSetAst(innerList, bdd); //if this condition is true then the innerList is a minimal cut set - }); - - return minimalCutSet; - } - - checkIfMinimalCutSetAst(innerList:AstNode[], bdd:AstNode[][]):boolean{ - for(const list of bdd){ - if(list.every(e=>innerList.includes(e)) && innerList !== list){ - return false; - } - } - - return true; - } - - generateCutSetsAst(allNodes:AstNode[]):AstNode[][]{ - - - //When there is no gate, return the component - const startingNode = this.getChildOfTopEventAst(allNodes); - if(isComponent(startingNode)){ - return [[startingNode]]; - } - //Evaluate the child of the top event and recursively the entire Fault Tree. - const cutSets = this.evaluateAst(startingNode, allNodes); - - return cutSets; - - } - - evaluateAst(node:AstNode, allNodes: AstNode[]): AstNode[][]{ + evaluate(node:AstNode, allNodes: AstNode[], idCache:IdCache): AstNode[][]{ let result:AstNode[][] = []; // we start with the top-most gate(child of topevent) and get all its children. - const children = this.getAllChildrenOfNodeAst(node); + const children = this.getAllChildrenOfNode(node); if(children.length === 0){return result;}; //if the node is an and/inhibit-gate we want to evaluate all children and concatenate all inner lists of one child with another. if(isGate(node) && (isAND(node.type) || isInhibitGate(node.type))){ for(const child of children){ if(isComponent(child) || isCondition(child)){ - result = this.concatAllListsAst([[child]], result); + result = this.concatAllLists([[child]], result, idCache); }else{ - result = this.concatAllListsAst(this.evaluateAst(child, allNodes), result); + result = this.concatAllLists(this.evaluate(child, allNodes, idCache), result, idCache); } } //if the node is an or-gate we want to evaluate all children and add every single inner list to the result. @@ -388,7 +114,7 @@ export class CutSetGenerator{ const orList = [child]; result.push(orList); }else{ - for(const list of this.evaluateAst(child, allNodes)){ //push every inner list of the child gate. + for(const list of this.evaluate(child, allNodes, idCache)){ //push every inner list of the child gate. result.push(list); } } @@ -404,7 +130,7 @@ export class CutSetGenerator{ //Example: With Children:[M1,M2,G1] and k=2 -> [[M1,M2],[M1,G1],[M2,G1]] . const combinations:AstNode[][]=[]; for(let i = k; i<=n; i++){ - for(const comb of this.getAllCombinationsAst(children, i)){ + for(const comb of this.getAllCombinations(children, i)){ combinations.push(comb); } } @@ -413,7 +139,7 @@ export class CutSetGenerator{ //Our result list should look like this -> [[M1,M2], [M1,C], [M2,C]]. for(const comb of combinations){ if(comb.some(e => isGate(e) && (isAND(e.type) || isInhibitGate(e.type) || isOR(e.type) || isKNGate(e.type)))){ - const evaluatedLists = this.evaluateGateInCombinationListAst(comb, allNodes); + const evaluatedLists = this.evaluateGateInCombinationList(comb, allNodes, idCache); for(const list of evaluatedLists){ result.push(list); } @@ -427,7 +153,14 @@ export class CutSetGenerator{ } - evaluateGateInCombinationListAst(innerList: AstNode[], allNodes:AstNode[]):AstNode[][]{ + /** + * Takes a list of components, conditions and gates and then removes the gates and inserts its evaluation in the list. This can result in multiple lists. + * @param innerList The list we want to evaluate. + * @param allNodes All Nodes in the graph. + * @param idCache The idCache of the generator context from the current graph. + * @returns A list of lists that is the result of inserting the evaluation of the gates in the given list. + */ + evaluateGateInCombinationList(innerList: AstNode[], allNodes:AstNode[], idCache:IdCache):AstNode[][]{ let result:AstNode[][] = []; const restList:AstNode[] = innerList; @@ -441,7 +174,7 @@ export class CutSetGenerator{ restList.splice(index, 1); i-=1; //and push the evaluation of the gate into the result list. - const tempLists = this.concatAllListsAst(this.evaluateAst(element, allNodes), result); + const tempLists = this.concatAllLists(this.evaluate(element, allNodes, idCache), result, idCache); result = []; for(const list of tempLists){ result.push(list); @@ -451,14 +184,20 @@ export class CutSetGenerator{ } //concatenate every element of the rest list with the result (should only be components/conditions). for(const list of restList){ - result = this.concatAllListsAst([[list]], result); + result = this.concatAllLists([[list]], result, idCache); } return result; } - getAllCombinationsAst(nodes:AstNode[], k:number):AstNode[][]{ + /** + * Gets all combinations of the elements in the given list with length k. + * @param nodes The list of nodes we want the combinations of. + * @param k The number of elements we want in an innerList. + * @returns the combinations of the elements in the given list with length k. + */ + getAllCombinations(nodes:AstNode[], k:number):AstNode[][]{ const combinations:AstNode[][] = []; if (k > nodes.length || k <= 0) { @@ -476,7 +215,7 @@ export class CutSetGenerator{ for(let i = 0; i):AstNode[][]{ const result: AstNode[][] = []; @@ -544,7 +299,7 @@ export class CutSetGenerator{ //Add only unique sets let newSet = innerA.concat(innerB); newSet = newSet.filter((e,i) => newSet.indexOf(e) === i); - if(this.indexOfArrayAst(newSet, result) === -1){ + if(this.indexOfArray(newSet, result, idCache) === -1){ result.push(newSet); } @@ -556,8 +311,14 @@ export class CutSetGenerator{ } - arrayEqualsAst(a:AstNode[], b:AstNode[]):boolean{ - /* const idCache = args.idCache; + /** + * Checks if array a and b are equal, + * @param a The first array we want to compare. + * @param b The second array we want to compaare. + * @param idCache The idCache of the generator context from the current graph. + * @returns True if they are equal and false if not. + */ + arrayEquals(a:AstNode[], b:AstNode[], idCache:IdCache):boolean{ const sort = (x:AstNode, y:AstNode):number => { let idX = idCache.getId(x); let idY = idCache.getId(y); @@ -567,20 +328,24 @@ export class CutSetGenerator{ return 0; } const sortA = a.sort(sort); - const sortB = b.sort(sort); */ + const sortB = b.sort(sort); - return a.length === b.length && a.every((e,i) => e === b[i]); - - + return a.length === b.length && sortA.every((e,i) => e === sortB[i]); } - - indexOfArrayAst(a:AstNode[], b:AstNode[][]):number{ + /** + * Gets the index of a list in a two-dimensional list of AstNodes, -1 otherwise. + * @param a The list we want the index of. + * @param b The two-dimensional list of AstNodes we want to search in. + * @param idCache The idCache of the generator context from the current graph. + * @returns the index of the list. + */ + indexOfArray(a:AstNode[], b:AstNode[][], idCache:IdCache):number{ let i = 0; for(const list of b){ - if(this.arrayEqualsAst(a, list)){ + if(this.arrayEquals(a, list, idCache)){ break; } i++; diff --git a/extension/src-language-server/fta/fta-diagram-generator.ts b/extension/src-language-server/fta/fta-diagram-generator.ts index e46cb46..229cd51 100644 --- a/extension/src-language-server/fta/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/fta-diagram-generator.ts @@ -16,7 +16,7 @@ */ import { AstNode } from 'langium'; -import { GeneratorContext, LangiumDiagramGenerator } from 'langium-sprotty'; +import { GeneratorContext, IdCache, LangiumDiagramGenerator } from 'langium-sprotty'; import { SLabel, SModelElement, SModelRoot } from 'sprotty-protocol'; import { Component, Condition, Gate, ModelFTA, TopEvent, isComponent, isCondition, isGate, isKNGate } from '../generated/ast'; import { FTAEdge, FTANode } from './fta-interfaces'; @@ -28,7 +28,7 @@ import { getAllGateTypes, getFTNodeType, getTargets } from './fta-utils'; export class FtaDiagramGenerator extends LangiumDiagramGenerator{ allNodes:AstNode[]; - //allEdges:FTAEdge[]; + idCache: IdCache; constructor(services: FtaServices){ super(services); } @@ -72,27 +72,13 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ - // filtering the nodes of the FTA graph - /* const ftaNodes: FTANode[] = []; - for (const node of ftaChildren) { - if (node.type === FTA_NODE_TYPE) { - ftaNodes.push(node as FTANode); - } - } - const ftaEdges:FTAEdge[] = []; - for(const edge of ftaChildren){ - if(edge.type === FTA_EDGE_TYPE){ - ftaEdges.push(edge as FTAEdge); - } - } - this.allNodes = ftaNodes; - this.allEdges = ftaEdges; */ - this.allNodes = model.components; this.allNodes = this.allNodes.concat(model.topEvent, ...model.conditions); allGates.forEach((value:AstNode[]) => { this.allNodes = this.allNodes.concat(...value); }); + + this.idCache = args.idCache; return { @@ -116,10 +102,14 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ public getNodes():AstNode[]{ return this.allNodes; } - /* public getEdges():FTAEdge[]{ - return this.allEdges; - } */ + /** + * Getter method for the idCache to get the ids for every node. + * @returns the idCache of generator context. + */ + public getCache():IdCache{ + return this.idCache; + } /** * Generates the edges for {@code node}. diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index 353bd08..7bc96cb 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -15,11 +15,10 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { IdCache, LangiumSprottySharedServices } from 'langium-sprotty'; import { Connection } from "vscode-languageserver"; import { FtaDiagramGenerator } from "./fta-diagram-generator"; import { FtaServices } from './fta-module'; -import { AstNode } from 'langium'; +import { cutSetToString, minimalCutSetToString } from './fta-utils'; /** @@ -40,27 +39,25 @@ export function addFTANotificationHandler(connection: Connection, ftaServices: F */ function addCutSetsHandler(connection: Connection, ftaServices: FtaServices):void{ connection.onRequest('generate/getCutSets', () =>{ - - const diagramGenerator = (ftaServices.diagram.DiagramGenerator) as FtaDiagramGenerator; const nodes = diagramGenerator.getNodes(); - //const edges = diagramGenerator.getEdges(); - + const idCache = diagramGenerator.getCache(); - const cutSets = ftaServices.bdd.Bdd.generateCutSetsAst(nodes); + const cutSets = ftaServices.bdd.Bdd.generateCutSets(nodes, idCache); + let cutSetsToString = cutSetToString(cutSets, idCache); - - return cutSets; + return cutSetsToString; }); connection.onRequest('generate/getMinimalCutSets', () =>{ const diagramGenerator = (ftaServices.diagram.DiagramGenerator) as FtaDiagramGenerator; const nodes = diagramGenerator.getNodes(); - //const edges = diagramGenerator.getEdges(); + const idCache = diagramGenerator.getCache(); - - /* const minimalCutSets = ftaServices.bdd.Bdd.determineMinimalCutSet(nodes, edges); - return minimalCutSets; */ + const minimalCutSets = ftaServices.bdd.Bdd.determineMinimalCutSet(nodes, idCache); + let minCutSetToString = minimalCutSetToString(minimalCutSets, idCache); + + return minCutSetToString; }); } diff --git a/extension/src-language-server/fta/fta-utils.ts b/extension/src-language-server/fta/fta-utils.ts index 186fb16..35b5297 100644 --- a/extension/src-language-server/fta/fta-utils.ts +++ b/extension/src-language-server/fta/fta-utils.ts @@ -16,8 +16,8 @@ */ import { AstNode } from 'langium'; +import { IdCache } from 'langium-sprotty'; import { Gate, isAND, isComponent, isCondition, isGate, isInhibitGate, isKNGate, isOR, isTopEvent } from '../generated/ast'; -import { FTANode } from './fta-interfaces'; import { FTNodeType } from './fta-model'; @@ -108,27 +108,50 @@ export function getAllGateTypes(gates: Gate[]): Map{ } /** - * Takes all (minimal) cut sets and returns a string that resembles it, so it can be displayed in the console. - * @param cutSets The (minimal) cut sets of the current Fault Tree. - * @returns A string that resembles the cut sets. - */ -export function CutSetToString(cutSets:FTANode[][]):string{ - let result = "["; + * Takes all cut sets and returns a string that resembles it, so it can be displayed in the console. + * @param cutSets The cut sets of the current Fault Tree. + * @param idCache The idCache of the generator context from the current graph. + * @returns a string that contains every cut set. + */ +export function cutSetToString(cutSets:AstNode[][], idCache:IdCache):string{ + let result = "The resulting " + cutSets.length + " cut sets are: \n"; + return setToString(cutSets, idCache, result); +} + +/** + * Takes all minimal cut sets and returns a string that resembles it, so it can be displayed in the console. + * @param minimalCutSets The minimal cut sets of the current Fault Tree. + * @param idCache The idCache of the generator context from the current graph. + * @returns a string that contains every minimal cut set. + */ +export function minimalCutSetToString(minimalCutSets:AstNode[][], idCache:IdCache):string{ + let result = "The resulting " + minimalCutSets.length + " minimal cut sets are: \n"; + return setToString(minimalCutSets, idCache, result); +} + + +/** + * Takes all (minimal) cut sets and returns a string that resembles it, so it can be displayed in the console. + * @param cutSets The (minimal) cut sets of the current Fault Tree. + * @returns A string that resembles the cut sets. +*/ +export function setToString(cutSets:AstNode[][], idCache:IdCache, result:string):string{ + result += "[" for(const set of cutSets){ result += "["; for(const element of set){ if(set.indexOf(element) === set.length -1){ - result += element.id; + result += idCache.getId(element); }else{ - result = result + element.id + ","; + result = result + idCache.getId(element) + ","; } } result += "]"; if(cutSets.indexOf(set) === cutSets.length -1){ - result += "] \n"; + result += "]\n"; }else{ - result += ", \n"; + result += ",\n"; } } diff --git a/extension/src-webview/options/cut-set-registry.ts b/extension/src-webview/options/cut-set-registry.ts index 5d4da6a..47e532e 100644 --- a/extension/src-webview/options/cut-set-registry.ts +++ b/extension/src-webview/options/cut-set-registry.ts @@ -52,17 +52,7 @@ export class CutSetsRegistry extends Registry{ if(SendCutSetAction.isThisAction(action)){ const dropDownOption = new DropDownMenuOption(); for(const set of action.cutSets){ - let id = "["; - for(const element of set.value){ - if(set.value.indexOf(element) === set.value.length -1){ - id += element.id; - }else{ - id = id + element.id + ","; - } - } - id += "]"; - - dropDownOption.availableValues.push({displayName: id , id: id}); + dropDownOption.availableValues.push({displayName: set.value , id: set.value}); } this._options.set('cut-sets', dropDownOption); diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index 5e53a1c..2cdb174 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -22,13 +22,10 @@ import { LspLabelEditActionHandler, SprottyLspEditVscodeExtension, WorkspaceEdit import { SprottyWebview } from 'sprotty-vscode/lib/sprotty-webview'; import * as vscode from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'; -import { FTANode } from '../src-language-server/fta/fta-interfaces'; -import { CutSetToString } from '../src-language-server/fta/fta-utils'; import { SendCutSetAction, UpdateViewAction } from './actions'; import { ContextTablePanel } from './context-table-panel'; import { StpaFormattingEditProvider } from './stpa-formatter'; import { StpaLspWebview } from './wview'; -import { AstNode } from 'langium'; export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { @@ -179,24 +176,24 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { //commands for computing and displaying the (minimal) cut sets of the fault tree. this.context.subscriptions.push( vscode.commands.registerCommand(this.extensionPrefix + '.generate.ftaCutSets', async () =>{ - const cutSets:AstNode[][] = await this.languageClient.sendRequest('generate/getCutSets'); + const cutSets:string = await this.languageClient.sendRequest('generate/getCutSets'); //Send cut sets to webview to display them in a dropdown menu. - //this.dispatchCutSetsToWebview(cutSets); + this.dispatchCutSetsToWebview(cutSets); const outputCutSets = vscode.window.createOutputChannel("All cut sets"); - outputCutSets.append("The resulting " + cutSets.length + " cut sets are: \n" /* + CutSetToString(cutSets) */); + outputCutSets.append(cutSets); outputCutSets.show(); }) ); this.context.subscriptions.push( vscode.commands.registerCommand(this.extensionPrefix + '.generate.ftaMinimalCutSets', async () =>{ - const minimalCutSets:AstNode[][] = await this.languageClient.sendRequest('generate/getMinimalCutSets'); + const minimalCutSets:string = await this.languageClient.sendRequest('generate/getMinimalCutSets'); - //this.dispatchCutSetsToWebview(minimalCutSets); + this.dispatchCutSetsToWebview(minimalCutSets); const outputMinimalCutSets = vscode.window.createOutputChannel("All minimal cut sets"); - outputMinimalCutSets.append("The resulting " + minimalCutSets.length + " minimal cut sets are: \n" /* + CutSetToString(minimalCutSets) */); + outputMinimalCutSets.append(minimalCutSets); outputMinimalCutSets.show(); }) ); @@ -205,9 +202,13 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { * Sends the cut sets to webview as a SendCutSetAction so that they can be displayed in a dropdown menu. * @param cutSets The (minimal) cut sets of the current Fault Tree. */ - protected dispatchCutSetsToWebview(cutSets:FTANode[][]):void{ + protected dispatchCutSetsToWebview(cutSets:string):void{ + cutSets = cutSets.substring(cutSets.indexOf("[")); + cutSets = cutSets.slice(1,-2); + let cutSetArray = cutSets.split(",\n"); + const cutSetDropDownList: { value: any; }[] = []; - for(const set of cutSets){ + for(const set of cutSetArray){ cutSetDropDownList.push({value: set}); } this.singleton?.dispatch({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); From b355067f1c97bb098d97b124a246cb58a5115e8f Mon Sep 17 00:00:00 2001 From: Orhan Date: Thu, 13 Jul 2023 12:10:06 +0200 Subject: [PATCH 21/61] yarn fixes --- extension/src-language-server/fta/fta-cutSet-generator.ts | 6 +++--- extension/src-language-server/fta/fta-message-handler.ts | 4 ++-- extension/src-language-server/fta/fta-utils.ts | 6 +++--- extension/src/language-extension.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/extension/src-language-server/fta/fta-cutSet-generator.ts b/extension/src-language-server/fta/fta-cutSet-generator.ts index 7e75004..8fc9fbc 100644 --- a/extension/src-language-server/fta/fta-cutSet-generator.ts +++ b/extension/src-language-server/fta/fta-cutSet-generator.ts @@ -320,13 +320,13 @@ export class CutSetGenerator{ */ arrayEquals(a:AstNode[], b:AstNode[], idCache:IdCache):boolean{ const sort = (x:AstNode, y:AstNode):number => { - let idX = idCache.getId(x); - let idY = idCache.getId(y); + const idX = idCache.getId(x); + const idY = idCache.getId(y); if(idX && idY){ return idX > idY ? -1 : 1; } return 0; - } + }; const sortA = a.sort(sort); const sortB = b.sort(sort); diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index 7bc96cb..18c6fd8 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -44,7 +44,7 @@ function addCutSetsHandler(connection: Connection, ftaServices: FtaServices):voi const idCache = diagramGenerator.getCache(); const cutSets = ftaServices.bdd.Bdd.generateCutSets(nodes, idCache); - let cutSetsToString = cutSetToString(cutSets, idCache); + const cutSetsToString = cutSetToString(cutSets, idCache); return cutSetsToString; }); @@ -55,7 +55,7 @@ function addCutSetsHandler(connection: Connection, ftaServices: FtaServices):voi const idCache = diagramGenerator.getCache(); const minimalCutSets = ftaServices.bdd.Bdd.determineMinimalCutSet(nodes, idCache); - let minCutSetToString = minimalCutSetToString(minimalCutSets, idCache); + const minCutSetToString = minimalCutSetToString(minimalCutSets, idCache); return minCutSetToString; }); diff --git a/extension/src-language-server/fta/fta-utils.ts b/extension/src-language-server/fta/fta-utils.ts index 35b5297..039dd54 100644 --- a/extension/src-language-server/fta/fta-utils.ts +++ b/extension/src-language-server/fta/fta-utils.ts @@ -114,7 +114,7 @@ export function getAllGateTypes(gates: Gate[]): Map{ * @returns a string that contains every cut set. */ export function cutSetToString(cutSets:AstNode[][], idCache:IdCache):string{ - let result = "The resulting " + cutSets.length + " cut sets are: \n"; + const result = "The resulting " + cutSets.length + " cut sets are: \n"; return setToString(cutSets, idCache, result); } @@ -125,7 +125,7 @@ export function cutSetToString(cutSets:AstNode[][], idCache:IdCache):st * @returns a string that contains every minimal cut set. */ export function minimalCutSetToString(minimalCutSets:AstNode[][], idCache:IdCache):string{ - let result = "The resulting " + minimalCutSets.length + " minimal cut sets are: \n"; + const result = "The resulting " + minimalCutSets.length + " minimal cut sets are: \n"; return setToString(minimalCutSets, idCache, result); } @@ -136,7 +136,7 @@ export function minimalCutSetToString(minimalCutSets:AstNode[][], idCache:IdCach * @returns A string that resembles the cut sets. */ export function setToString(cutSets:AstNode[][], idCache:IdCache, result:string):string{ - result += "[" + result += "["; for(const set of cutSets){ result += "["; diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index 2cdb174..e0b924c 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -205,7 +205,7 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { protected dispatchCutSetsToWebview(cutSets:string):void{ cutSets = cutSets.substring(cutSets.indexOf("[")); cutSets = cutSets.slice(1,-2); - let cutSetArray = cutSets.split(",\n"); + const cutSetArray = cutSets.split(",\n"); const cutSetDropDownList: { value: any; }[] = []; for(const set of cutSetArray){ From cd33a9ac791296faaca60a17efd0726d936ce825 Mon Sep 17 00:00:00 2001 From: Orhan Date: Thu, 13 Jul 2023 13:09:30 +0200 Subject: [PATCH 22/61] comments --- extension/src-language-server/fta/fta-cutSet-generator.ts | 4 ++-- extension/src-language-server/fta/fta-module.ts | 6 ------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/extension/src-language-server/fta/fta-cutSet-generator.ts b/extension/src-language-server/fta/fta-cutSet-generator.ts index 8fc9fbc..97ad889 100644 --- a/extension/src-language-server/fta/fta-cutSet-generator.ts +++ b/extension/src-language-server/fta/fta-cutSet-generator.ts @@ -312,7 +312,7 @@ export class CutSetGenerator{ } /** - * Checks if array a and b are equal, + * Checks if array a and b are equal by sorting them and comparing their values. * @param a The first array we want to compare. * @param b The second array we want to compaare. * @param idCache The idCache of the generator context from the current graph. @@ -336,7 +336,7 @@ export class CutSetGenerator{ } /** - * Gets the index of a list in a two-dimensional list of AstNodes, -1 otherwise. + * Gets the index of a list if it's contained in a two-dimensional list of AstNodes or -1 otherwise. * @param a The list we want the index of. * @param b The two-dimensional list of AstNodes we want to search in. * @param idCache The idCache of the generator context from the current graph. diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index 1d30d0f..b80b0f4 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -28,12 +28,6 @@ import { FtaLayoutConfigurator } from './fta-layout-config'; import { FtaValidationRegistry, FtaValidator } from './fta-validator'; - - - - - - /** * Declaration of custom services - add your own service classes here. */ From f1a891afa23f6a5186a76fa7c3919396fa4a3101 Mon Sep 17 00:00:00 2001 From: Orhan Date: Mon, 17 Jul 2023 13:23:01 +0200 Subject: [PATCH 23/61] conditions validator --- extension/src-language-server/fta/fta-validator.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/extension/src-language-server/fta/fta-validator.ts b/extension/src-language-server/fta/fta-validator.ts index d68276f..fc42e94 100644 --- a/extension/src-language-server/fta/fta-validator.ts +++ b/extension/src-language-server/fta/fta-validator.ts @@ -48,7 +48,7 @@ export class FtaValidator { } /** - * Prevent multiple components and gates from having the same identifier. + * Prevent multiple components, conditions and gates from having the same identifier. * @param model The model to validate. * @param accept */ @@ -60,6 +60,12 @@ export class FtaValidator { } componentNames.add(c.name); }); + model.conditions.forEach(c => { + if(componentNames.has(c.name)){ + accept('error', `Condition has non-unique name '${c.name}'.`, {node: c, property: 'name'}); + } + componentNames.add(c.name); + }) const gateNames = new Set(); model.gates.forEach(g => { From 45dcacc3d0cc42be8ea069652cf3975756a204ba Mon Sep 17 00:00:00 2001 From: Orhan Date: Mon, 17 Jul 2023 13:59:14 +0200 Subject: [PATCH 24/61] yarn lint fixes --- extension/src-language-server/fta/fta-validator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src-language-server/fta/fta-validator.ts b/extension/src-language-server/fta/fta-validator.ts index fc42e94..638f81d 100644 --- a/extension/src-language-server/fta/fta-validator.ts +++ b/extension/src-language-server/fta/fta-validator.ts @@ -65,7 +65,7 @@ export class FtaValidator { accept('error', `Condition has non-unique name '${c.name}'.`, {node: c, property: 'name'}); } componentNames.add(c.name); - }) + }); const gateNames = new Set(); model.gates.forEach(g => { From 34a6d0b45fad3df6b7b43646db0c24a3351b8a81 Mon Sep 17 00:00:00 2001 From: Orhan Date: Tue, 18 Jul 2023 12:08:01 +0200 Subject: [PATCH 25/61] deleted unneccessary code --- extension/src-webview/css/fta-diagram.css | 152 +--------------------- extension/src-webview/css/fta-theme.css | 21 +-- extension/src-webview/fta-views.tsx | 2 +- 3 files changed, 9 insertions(+), 166 deletions(-) diff --git a/extension/src-webview/css/fta-diagram.css b/extension/src-webview/css/fta-diagram.css index fde2b84..2d940af 100644 --- a/extension/src-webview/css/fta-diagram.css +++ b/extension/src-webview/css/fta-diagram.css @@ -17,169 +17,27 @@ @import "./fta-theme.css"; -.vscode-dark .fta-node[aspect="0"] { +.vscode-dark .fta-node { fill: var(--fta-dark-node); stroke: var(--fta-light-node); stroke-width: 2; } -.vscode-dark .fta-node[aspect="1"] { - fill: var(--fta-dark-node); - stroke: var(--fta-light-node); - stroke-width: 2; -} -.vscode-dark .fta-node[aspect="2"] { - fill: var(--fta-dark-node); - stroke: var(--fta-light-node); - stroke-width: 2; -} - -.vscode-dark .fta-node[aspect="3"] { - fill: var(--fta-dark-node); - stroke: var(--fta-light-node); - stroke-width: 2; -} - -.vscode-dark .fta-node[aspect="4"] { - fill: var(--fta-dark-node); - stroke: var(--fta-light-node); - stroke-width: 2; -} - -.vscode-dark .fta-node[aspect="5"] { - fill: var(--fta-dark-node); - stroke: var(--fta-light-node); - stroke-width: 2; -} - -.vscode-dark .fta-node[aspect="6"] { - fill: var(--fta-dark-node); +.vscode-dark .fta-edge { stroke: var(--fta-light-node); - stroke-width: 2; -} - - -.vscode-dark .fta-edge[aspect="0"], -.vscode-dark .fta-edge-arrow[aspect="0"] { - stroke: var(--fta-light-node); -} - -.vscode-dark .fta-edge[aspect="1"], -.vscode-dark .fta-edge-arrow[aspect="1"] { - stroke: var(--fta-light-node); -} - -.vscode-dark .fta-edge[aspect="2"], -.vscode-dark .fta-edge-arrow[aspect="2"] { - stroke: var(--fta-light-node); -} - -.vscode-dark .fta-edge[aspect="3"], -.vscode-dark .fta-edge-arrow[aspect="3"] { - stroke: var(--fta-light-node); -} - -.vscode-dark .fta-edge[aspect="4"], -.vscode-dark .fta-edge-arrow[aspect="4"] { - stroke: var(--fta-light-node); -} - -.vscode-dark .fta-edge[aspect="5"], -.vscode-dark .fta-edge-arrow[aspect="5"] { - stroke: var(--fta-light-node); -} - -.vscode-dark .fta-edge[aspect="6"], -.vscode-dark .fta-edge-arrow[aspect="6"] { - stroke: var(--fta-light-node); -} - - - - -.vscode-light .fta-node[aspect="0"] { - fill: var(--fta-light-node); - stroke: black; - stroke-width: 2; } -.vscode-light .fta-node[aspect="1"] { - fill: var(--fta-light-node); - stroke: black; - stroke-width: 2; -} -.vscode-light .fta-node[aspect="2"] { +.vscode-light .fta-node { fill: var(--fta-light-node); stroke: black; stroke-width: 2; } -.vscode-light .fta-node[aspect="3"] { - fill: var(--fta-light-node); - stroke: black; - stroke-width: 2; -} - -.vscode-light .fta-node[aspect="4"] { - fill: var(--fta-light-node); - stroke: black; - stroke-width: 2; -} - -.vscode-light .fta-node[aspect="5"] { - fill: var(--fta-light-node); - stroke: black; - stroke-width: 2; -} - -.vscode-light .fta-node[aspect="6"] { - fill: var(--fta-light-node); - stroke: black; - stroke-width: 2; -} - - -.vscode-light .fta-edge[aspect="0"], -.vscode-light .fta-edge-arrow[aspect="0"] { - stroke: var(--fta-dark-node); - stroke-width: 2; -} - -.vscode-light .fta-edge[aspect="1"], -.vscode-light .fta-edge-arrow[aspect="1"] { - stroke: var(--fta-dark-node); - stroke-width: 2; -} - -.vscode-light .fta-edge[aspect="2"], -.vscode-light .fta-edge-arrow[aspect="2"] { - stroke: var(--fta-dark-node); - stroke-width: 2; -} - -.vscode-light .fta-edge[aspect="3"], -.vscode-light .fta-edge-arrow[aspect="3"] { - stroke: var(--fta-dark-node); - stroke-width: 2; -} - -.vscode-light .fta-edge[aspect="4"], -.vscode-light .fta-edge-arrow[aspect="4"] { - stroke: var(--fta-dark-node); - stroke-width: 2; -} - -.vscode-light .fta-edge[aspect="5"], -.vscode-light .fta-edge-arrow[aspect="5"] { - stroke: var(--fta-dark-node); - stroke-width: 2; -} -.vscode-light .fta-edge[aspect="6"], -.vscode-light .fta-edge-arrow[aspect="6"] { - stroke: var(--fta-dark-node); +.vscode-light .fta-edge { + stroke: var(--fta-darker-node); stroke-width: 2; } diff --git a/extension/src-webview/css/fta-theme.css b/extension/src-webview/css/fta-theme.css index cfba843..f522cf2 100644 --- a/extension/src-webview/css/fta-theme.css +++ b/extension/src-webview/css/fta-theme.css @@ -18,25 +18,10 @@ :root { - /* Colors for fta aspects */ - - --fta-topevent-dark: #AD0000; - --fta-component-dark: #A85400; - --fta-condition-dark: #006600; - --fta-and-dark: purple; - --fta-or-dark: #0054A8; - --fta-kn-dark: #006161; - --fta-inhibit-dark: #616100; + /* Colors for fta nodes */ - --fta-dark-node: #ffffff00; + --fta-darker-node: black; --fta-light-node: white; - - --fta-topevent-light: #FF2828; - --fta-component-light: #ffa852; - --fta-condition-light: #00eb00; - --fta-and-light: #ff6bff; - --fta-or-light: #66b3ff; - --fta-kn-light: #00cccc; - --fta-inhibit-light: #cccc00; + --fta-dark-node: #ffffff00; } diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index 749dddc..b5363d3 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -115,7 +115,7 @@ export class FTANodeView extends RectangularNodeView { const hidden = !node.highlight; return {element} From 6a1c6f3b5af940b8210bcd6ffac7b68571d251eb Mon Sep 17 00:00:00 2001 From: Orhan Date: Wed, 9 Aug 2023 14:22:58 +0200 Subject: [PATCH 26/61] comment fta-views --- extension/src-webview/fta-views.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index b5363d3..0b970da 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -123,6 +123,12 @@ export class FTANodeView extends RectangularNodeView { ; } + /** + * Takes a node and checks if it is connected to a highlighted node. + * @param node The node we want to check. + * @param set The set of all highlighted nodes. + * @returns True if the node is connected to a node from the set or false otherwise. + */ checkIfHighlighted(node: FTANode, set: any):boolean{ for(const edge of node.outgoingEdges){ let target = (edge.target as FTANode); From af6b41f6e8bbec6ce8265ab25c31b3135ef15b8f Mon Sep 17 00:00:00 2001 From: Orhan Date: Sat, 12 Aug 2023 17:06:43 +0200 Subject: [PATCH 27/61] outputChannel function --- extension/src/language-extension.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index e0b924c..28031ae 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -181,9 +181,7 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { //Send cut sets to webview to display them in a dropdown menu. this.dispatchCutSetsToWebview(cutSets); - const outputCutSets = vscode.window.createOutputChannel("All cut sets"); - outputCutSets.append(cutSets); - outputCutSets.show(); + this.createOutputChannel(cutSets, "All cut sets"); }) ); this.context.subscriptions.push( @@ -192,9 +190,7 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { this.dispatchCutSetsToWebview(minimalCutSets); - const outputMinimalCutSets = vscode.window.createOutputChannel("All minimal cut sets"); - outputMinimalCutSets.append(minimalCutSets); - outputMinimalCutSets.show(); + this.createOutputChannel(minimalCutSets, "All minimal cut sets"); }) ); } @@ -214,7 +210,16 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { this.singleton?.dispatch({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); } - + /** + * Creates an output channel with the given name and prints the given cut sets. + * @param cutSets The cut sets to print. + * @param channelName The name of the channel. + */ + protected createOutputChannel(cutSets:string, channelName:string):void{ + const outputCutSets = vscode.window.createOutputChannel(channelName); + outputCutSets.append(cutSets); + outputCutSets.show(); + } /** * Creates a quickpick containing the values "true" and "false". The selected value is set for the From 0d6619f4b932f69a7c116d2d45fa102f36cd60b2 Mon Sep 17 00:00:00 2001 From: Orhan Date: Sun, 13 Aug 2023 14:13:47 +0200 Subject: [PATCH 28/61] more comments --- extension/src-webview/fta-views.tsx | 4 +++- extension/src-webview/options/cut-set-registry.ts | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index 0b970da..0fe00e7 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -88,12 +88,14 @@ export class FTANodeView extends RectangularNodeView { break; } - //highlight every node that is in the selected cut set. + //highlight every node that is in the selected cut set or on the path to the top event. let set = this.cutSetsRegistry.getCurrentValue(); if(set !== undefined){ + //highlight all when the empty cut set is selected if(set === '-' ){ node.highlight = true; }else{ + //unhighlight every node first and then only highlight the correct ones. node.highlight = false; if(node.nodeType === FTNodeType.COMPONENT || node.nodeType === FTNodeType.CONDITION){ if(set.includes(node.id)){ diff --git a/extension/src-webview/options/cut-set-registry.ts b/extension/src-webview/options/cut-set-registry.ts index 47e532e..22c685e 100644 --- a/extension/src-webview/options/cut-set-registry.ts +++ b/extension/src-webview/options/cut-set-registry.ts @@ -66,11 +66,13 @@ export class CutSetsRegistry extends Registry{ } getCurrentValue():any{ + //if the cut sets were not requested yet, there is nothing to highlight if(this._options.get('cut-sets')?.availableValues.length === 1){ return undefined; } const selectedCutSet:{ displayName: string; id: string } = this._options.get('cut-sets')?.currentValue; if(selectedCutSet){ + //slice the brackets at the start and at the end. const selected = selectedCutSet.displayName.slice(1,-1); if(selected === '-'){ return '-'; From bdecf97b396430860d123572d59eb8fdcda9cab6 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Fri, 18 Aug 2023 09:01:59 +0200 Subject: [PATCH 29/61] fixed automatic update --- extension/src/language-extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index 28031ae..681ecbd 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -313,7 +313,7 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } }; - const fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/*.(stpa|fta)'); + const fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/*.{stpa,fta}'); context.subscriptions.push(fileSystemWatcher); // Options to control the language client From 513c58f7533e757b47cab3ce229570b70a06123b Mon Sep 17 00:00:00 2001 From: Orhan Date: Tue, 29 Aug 2023 17:45:03 +0200 Subject: [PATCH 30/61] highlight selected components in red --- extension/src-webview/css/fta-diagram.css | 12 ++++++++++++ extension/src-webview/css/fta-theme.css | 2 ++ extension/src-webview/fta-views.tsx | 12 ++++++++++-- extension/src/language-extension.ts | 2 +- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/extension/src-webview/css/fta-diagram.css b/extension/src-webview/css/fta-diagram.css index 2d940af..6372b17 100644 --- a/extension/src-webview/css/fta-diagram.css +++ b/extension/src-webview/css/fta-diagram.css @@ -46,3 +46,15 @@ stroke-width: 0.5; font-size: 10px; } + +.fta-hidden { + opacity: 0.1; +} + +.vscode-dark .fta-highlight-node{ + stroke: var(--fta-highlight-node); +} + +.vscode-light .fta-highlight-node{ + stroke: var(--fta-highlight-node); +} \ No newline at end of file diff --git a/extension/src-webview/css/fta-theme.css b/extension/src-webview/css/fta-theme.css index f522cf2..45ab116 100644 --- a/extension/src-webview/css/fta-theme.css +++ b/extension/src-webview/css/fta-theme.css @@ -23,5 +23,7 @@ --fta-darker-node: black; --fta-light-node: white; --fta-dark-node: #ffffff00; + --fta-highlight-node: #FF2828; + } diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index 0fe00e7..ab73f22 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -45,7 +45,7 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { // if an FTANode is selected, the components not connected to it should fade out const hidden = edge.type == FTA_EDGE_TYPE && !(edge as FTAEdge).highlight; - return ; + return ; } } @@ -90,6 +90,7 @@ export class FTANodeView extends RectangularNodeView { //highlight every node that is in the selected cut set or on the path to the top event. let set = this.cutSetsRegistry.getCurrentValue(); + let bool = false; if(set !== undefined){ //highlight all when the empty cut set is selected if(set === '-' ){ @@ -98,12 +99,18 @@ export class FTANodeView extends RectangularNodeView { //unhighlight every node first and then only highlight the correct ones. node.highlight = false; if(node.nodeType === FTNodeType.COMPONENT || node.nodeType === FTNodeType.CONDITION){ + //node is component or condition and in the selected cut set. if(set.includes(node.id)){ node.highlight = true; + bool = true; + }else{ + //all other components and conditions are not highlighted. node.highlight = false; + bool= false; } }else{ + //check if a gate should be highlighted if(this.checkIfHighlighted(node, set) === true){ node.highlight = true; }else{ @@ -119,7 +126,8 @@ export class FTANodeView extends RectangularNodeView { return + class-fta-hidden={hidden} + class-fta-highlight-node={bool}> {element} {context.renderChildren(node)} ; diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index 28031ae..68cee54 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -313,7 +313,7 @@ export class StpaLspVscodeExtension extends SprottyLspEditVscodeExtension { debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } }; - const fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/*.(stpa|fta)'); + const fileSystemWatcher = vscode.workspace.createFileSystemWatcher('**/*.{stpa, fta}'); context.subscriptions.push(fileSystemWatcher); // Options to control the language client From 7a1301bcf70eea952f2cb8475f024335f48a3980 Mon Sep 17 00:00:00 2001 From: Orhan Date: Wed, 30 Aug 2023 17:58:53 +0200 Subject: [PATCH 31/61] Final version --- extension/src-language-server/fta/fta-cutSet-generator.ts | 5 +++-- extension/src-webview/fta-views.tsx | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/extension/src-language-server/fta/fta-cutSet-generator.ts b/extension/src-language-server/fta/fta-cutSet-generator.ts index 97ad889..0f0d33d 100644 --- a/extension/src-language-server/fta/fta-cutSet-generator.ts +++ b/extension/src-language-server/fta/fta-cutSet-generator.ts @@ -114,7 +114,8 @@ export class CutSetGenerator{ const orList = [child]; result.push(orList); }else{ - for(const list of this.evaluate(child, allNodes, idCache)){ //push every inner list of the child gate. + //push every inner list of the child gate. + for(const list of this.evaluate(child, allNodes, idCache)){ result.push(list); } } @@ -135,7 +136,7 @@ export class CutSetGenerator{ } } - //Now we want to evaluate G1 (e.g evaluation(G1) = [[C]]). + //Now we want to evaluate G1 from the example above (e.g evaluation(G1) = [[C]]). //Our result list should look like this -> [[M1,M2], [M1,C], [M2,C]]. for(const comb of combinations){ if(comb.some(e => isGate(e) && (isAND(e.type) || isInhibitGate(e.type) || isOR(e.type) || isKNGate(e.type)))){ diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index ab73f22..f8d3e1f 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -90,7 +90,7 @@ export class FTANodeView extends RectangularNodeView { //highlight every node that is in the selected cut set or on the path to the top event. let set = this.cutSetsRegistry.getCurrentValue(); - let bool = false; + let onlyInCutSet = false; if(set !== undefined){ //highlight all when the empty cut set is selected if(set === '-' ){ @@ -102,12 +102,12 @@ export class FTANodeView extends RectangularNodeView { //node is component or condition and in the selected cut set. if(set.includes(node.id)){ node.highlight = true; - bool = true; + onlyInCutSet = true; }else{ //all other components and conditions are not highlighted. node.highlight = false; - bool= false; + onlyInCutSet= false; } }else{ //check if a gate should be highlighted @@ -127,7 +127,7 @@ export class FTANodeView extends RectangularNodeView { class-fta-node={true} class-mouseover={node.hoverFeedback} class-fta-hidden={hidden} - class-fta-highlight-node={bool}> + class-fta-highlight-node={onlyInCutSet}> {element} {context.renderChildren(node)} ; From e9b5a62d7546e24ef2a44f20e73b9ae3a6f16791 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Wed, 20 Sep 2023 09:01:16 +0200 Subject: [PATCH 32/61] fix merge --- .../src-language-server/fta/fta-module.ts | 7 +- .../stpa/diagram/stpa-diagramServer.ts | 148 +++++++++--------- .../src-language-server/stpa/stpa-module.ts | 4 +- extension/src/extension.ts | 2 +- extension/src/language-extension.ts | 13 +- 5 files changed, 85 insertions(+), 89 deletions(-) diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index b80b0f4..979078e 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -21,7 +21,7 @@ import { DefaultDiagramServerManager, DiagramActionNotification, LangiumSprottyS import { DefaultElementFilter, ElkFactory, ElkLayoutEngine, IElementFilter, ILayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; import { DiagramOptions } from 'sprotty-protocol'; import { URI } from 'vscode-uri'; -import { StpaDiagramServer } from '../stpa/stpa-diagramServer'; +import { StpaDiagramServer } from '../stpa/diagram/stpa-diagramServer'; import { CutSetGenerator } from './fta-cutSet-generator'; import { FtaDiagramGenerator } from './fta-diagram-generator'; import { FtaLayoutConfigurator } from './fta-layout-config'; @@ -90,7 +90,10 @@ export const ftaDiagramServerFactory = } return new StpaDiagramServer(async action => { connection?.sendNotification(DiagramActionNotification.type, { clientId, action }); - }, language.diagram, clientId); + }, + language.diagram, + clientId, + connection); }; }; /** diff --git a/extension/src-language-server/stpa/diagram/stpa-diagramServer.ts b/extension/src-language-server/stpa/diagram/stpa-diagramServer.ts index b29c330..d04213a 100644 --- a/extension/src-language-server/stpa/diagram/stpa-diagramServer.ts +++ b/extension/src-language-server/stpa/diagram/stpa-diagramServer.ts @@ -50,21 +50,21 @@ import { setSafetyRequirementGraphOptions, setScenarioWithFilteredUCAGraphOptions, setScenarioWithNoUCAGraphOptions, - setSystemConstraintGraphOptions + setSystemConstraintGraphOptions, } from "../result-report/svg-generator"; import { StpaSynthesisOptions, filteringUCAsID } from "./synthesis-options"; export class StpaDiagramServer extends DiagramServer { - protected stpaOptions: StpaSynthesisOptions; + protected stpaOptions: StpaSynthesisOptions | undefined; clientId: string; protected connection: Connection | undefined; constructor( dispatch: (action: A) => Promise, services: DiagramServices, - synthesisOptions: StpaSynthesisOptions, clientId: string, - connection: Connection | undefined + connection: Connection | undefined, + synthesisOptions?: StpaSynthesisOptions ) { super(dispatch, services); this.stpaOptions = synthesisOptions; @@ -100,59 +100,61 @@ export class StpaDiagramServer extends DiagramServer { * @returns */ async handleGenerateSVGDiagrams(action: GenerateSVGsAction): Promise { - diagramSizes = {}; - const setSynthesisOption = { - kind: SetSynthesisOptionsAction.KIND, - options: this.stpaOptions.getSynthesisOptions().map((option) => option.synthesisOption), - } as SetSynthesisOptionsAction; - // save current option values - saveOptions(this.stpaOptions); - // control structure svg - setControlStructureOptions(this.stpaOptions); - await this.createSVG(setSynthesisOption, action.uri, CONTROL_STRUCTURE_PATH); - // hazard graph svg - setHazardGraphOptions(this.stpaOptions); - await this.createSVG(setSynthesisOption, action.uri, HAZARD_PATH); - // system constraint graph svg - setSystemConstraintGraphOptions(this.stpaOptions); - await this.createSVG(setSynthesisOption, action.uri, SYSTEM_CONSTRAINT_PATH); - // responsibility graph svg - setResponsibilityGraphOptions(this.stpaOptions); - await this.createSVG(setSynthesisOption, action.uri, RESPONSIBILITY_PATH); - - // filtered uca graph svg - const filteringUcaOption = this.stpaOptions - .getSynthesisOptions() - .find((option) => option.synthesisOption.id === filteringUCAsID); - for (const value of (filteringUcaOption?.synthesisOption as DropDownOption).availableValues) { - setFilteredUcaGraphOptions(this.stpaOptions, value.id); - await this.createSVG(setSynthesisOption, action.uri, FILTERED_UCA_PATH(value.id)); - } + if (this.stpaOptions) { + diagramSizes = {}; + const setSynthesisOption = { + kind: SetSynthesisOptionsAction.KIND, + options: this.stpaOptions.getSynthesisOptions().map((option) => option.synthesisOption), + } as SetSynthesisOptionsAction; + // save current option values + saveOptions(this.stpaOptions); + // control structure svg + setControlStructureOptions(this.stpaOptions); + await this.createSVG(setSynthesisOption, action.uri, CONTROL_STRUCTURE_PATH); + // hazard graph svg + setHazardGraphOptions(this.stpaOptions); + await this.createSVG(setSynthesisOption, action.uri, HAZARD_PATH); + // system constraint graph svg + setSystemConstraintGraphOptions(this.stpaOptions); + await this.createSVG(setSynthesisOption, action.uri, SYSTEM_CONSTRAINT_PATH); + // responsibility graph svg + setResponsibilityGraphOptions(this.stpaOptions); + await this.createSVG(setSynthesisOption, action.uri, RESPONSIBILITY_PATH); + + // filtered uca graph svg + const filteringUcaOption = this.stpaOptions + .getSynthesisOptions() + .find((option) => option.synthesisOption.id === filteringUCAsID); + for (const value of (filteringUcaOption?.synthesisOption as DropDownOption).availableValues) { + setFilteredUcaGraphOptions(this.stpaOptions, value.id); + await this.createSVG(setSynthesisOption, action.uri, FILTERED_UCA_PATH(value.id)); + } - // filtered controller constraint graph svg - for (const value of (filteringUcaOption?.synthesisOption as DropDownOption).availableValues) { - setControllerConstraintWithFilteredUcaGraphOptions(this.stpaOptions, value.id); - await this.createSVG(setSynthesisOption, action.uri, FILTERED_CONTROLLER_CONSTRAINT_PATH(value.id)); - } + // filtered controller constraint graph svg + for (const value of (filteringUcaOption?.synthesisOption as DropDownOption).availableValues) { + setControllerConstraintWithFilteredUcaGraphOptions(this.stpaOptions, value.id); + await this.createSVG(setSynthesisOption, action.uri, FILTERED_CONTROLLER_CONSTRAINT_PATH(value.id)); + } - // filtered scenario graph svg - for (const value of (filteringUcaOption?.synthesisOption as DropDownOption).availableValues) { - setScenarioWithFilteredUCAGraphOptions(this.stpaOptions, value.id); - await this.createSVG(setSynthesisOption, action.uri, FILTERED_SCENARIO_PATH(value.id)); + // filtered scenario graph svg + for (const value of (filteringUcaOption?.synthesisOption as DropDownOption).availableValues) { + setScenarioWithFilteredUCAGraphOptions(this.stpaOptions, value.id); + await this.createSVG(setSynthesisOption, action.uri, FILTERED_SCENARIO_PATH(value.id)); + } + // scenario with hazard svg graph + setScenarioWithNoUCAGraphOptions(this.stpaOptions); + await this.createSVG(setSynthesisOption, action.uri, SCENARIO_WITH_HAZARDS_PATH); + + // safety requirement svg graph + setSafetyRequirementGraphOptions(this.stpaOptions); + await this.createSVG(setSynthesisOption, action.uri, SAFETY_REQUIREMENT_PATH); + // complete graph svg + setRelationshipGraphOptions(this.stpaOptions); + await this.createSVG(setSynthesisOption, action.uri, COMPLETE_GRAPH_PATH); + // reset options + resetOptions(this.stpaOptions); + await this.handleSetSynthesisOption(setSynthesisOption); } - // scenario with hazard svg graph - setScenarioWithNoUCAGraphOptions(this.stpaOptions); - await this.createSVG(setSynthesisOption, action.uri, SCENARIO_WITH_HAZARDS_PATH); - - // safety requirement svg graph - setSafetyRequirementGraphOptions(this.stpaOptions); - await this.createSVG(setSynthesisOption, action.uri, SAFETY_REQUIREMENT_PATH); - // complete graph svg - setRelationshipGraphOptions(this.stpaOptions); - await this.createSVG(setSynthesisOption, action.uri, COMPLETE_GRAPH_PATH); - // reset options - resetOptions(this.stpaOptions); - await this.handleSetSynthesisOption(setSynthesisOption); return Promise.resolve(); } @@ -178,25 +180,27 @@ export class StpaDiagramServer extends DiagramServer { } protected async handleSetSynthesisOption(action: SetSynthesisOptionsAction): Promise { - for (const option of action.options) { - const opt = this.stpaOptions - .getSynthesisOptions() - .find((synOpt) => synOpt.synthesisOption.id === option.id); - if (opt) { - opt.currentValue = option.currentValue; - opt.synthesisOption.currentValue = option.currentValue; - // for dropdown menu options more must be done - if ((opt.synthesisOption as DropDownOption).currentId) { - (opt.synthesisOption as DropDownOption).currentId = option.currentValue; - this.dispatch({ - kind: UpdateOptionsAction.KIND, - valuedSynthesisOptions: this.stpaOptions.getSynthesisOptions(), - clientId: this.clientId, - }); + if (this.stpaOptions) { + for (const option of action.options) { + const opt = this.stpaOptions + .getSynthesisOptions() + .find((synOpt) => synOpt.synthesisOption.id === option.id); + if (opt) { + opt.currentValue = option.currentValue; + opt.synthesisOption.currentValue = option.currentValue; + // for dropdown menu options more must be done + if ((opt.synthesisOption as DropDownOption).currentId) { + (opt.synthesisOption as DropDownOption).currentId = option.currentValue; + this.dispatch({ + kind: UpdateOptionsAction.KIND, + valuedSynthesisOptions: this.stpaOptions.getSynthesisOptions(), + clientId: this.clientId, + }); + } } } + await this.updateView(this.state.options); } - await this.updateView(this.state.options); return Promise.resolve(); } @@ -213,7 +217,7 @@ export class StpaDiagramServer extends DiagramServer { // ensures the the filterUCA option is correct this.dispatch({ kind: UpdateOptionsAction.KIND, - valuedSynthesisOptions: this.stpaOptions.getSynthesisOptions(), + valuedSynthesisOptions: this.stpaOptions?.getSynthesisOptions() ?? [], clientId: this.clientId, }); } catch (err) { @@ -226,7 +230,7 @@ export class StpaDiagramServer extends DiagramServer { await super.handleRequestModel(action); this.dispatch({ kind: UpdateOptionsAction.KIND, - valuedSynthesisOptions: this.stpaOptions.getSynthesisOptions(), + valuedSynthesisOptions: this.stpaOptions?.getSynthesisOptions() ?? [], clientId: this.clientId, }); } diff --git a/extension/src-language-server/stpa/stpa-module.ts b/extension/src-language-server/stpa/stpa-module.ts index a546156..0b9bd3a 100644 --- a/extension/src-language-server/stpa/stpa-module.ts +++ b/extension/src-language-server/stpa/stpa-module.ts @@ -141,9 +141,9 @@ export const stpaDiagramServerFactory = ( connection?.sendNotification(DiagramActionNotification.type, { clientId, action }); }, language.diagram, - language.options.StpaSynthesisOptions, clientId, - connection + connection, + language.options.StpaSynthesisOptions ); }; }; diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 3c2a71e..d649308 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -47,7 +47,7 @@ export function activate(context: vscode.ExtensionContext): void { extensionUri: context.extensionUri, defaultDiagramType: 'stpa', languageClient, - supportedFileExtensions: ['.stpa'], + supportedFileExtensions: ['.stpa', '.fta'], singleton: true, messenger: new Messenger({ ignoreHiddenViews: false }) }, 'pasta'); diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index e944b06..54b39dd 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -20,22 +20,11 @@ import { createFileUri } from "sprotty-vscode"; import { SprottyDiagramIdentifier } from "sprotty-vscode-protocol"; import { LspWebviewEndpoint, LspWebviewPanelManager, LspWebviewPanelManagerOptions } from "sprotty-vscode/lib/lsp"; import * as vscode from "vscode"; -import { GenerateSVGsAction } from "./actions"; +import { GenerateSVGsAction, SendCutSetAction } from "./actions"; import { ContextTablePanel } from "./context-table-panel"; import { StpaFormattingEditProvider } from "./stpa-formatter"; import { applyTextEdits, collectOptions, createFile } from "./utils"; import { StpaLspWebview } from "./wview"; -import * as path from 'path'; -import { ActionMessage, JsonMap, SelectAction } from 'sprotty-protocol'; -import { SprottyDiagramIdentifier } from 'sprotty-vscode/lib/lsp'; -import { LspLabelEditActionHandler, SprottyLspEditVscodeExtension, WorkspaceEditActionHandler } from "sprotty-vscode/lib/lsp/editing"; -import { SprottyWebview } from 'sprotty-vscode/lib/sprotty-webview'; -import * as vscode from 'vscode'; -import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'; -import { SendCutSetAction, UpdateViewAction } from './actions'; -import { ContextTablePanel } from './context-table-panel'; -import { StpaFormattingEditProvider } from './stpa-formatter'; -import { StpaLspWebview } from './wview'; export class StpaLspVscodeExtension extends LspWebviewPanelManager { protected extensionPrefix: string; From 8d6f8c060743ec2b13e099566405a40dd1653792 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Wed, 20 Sep 2023 11:33:34 +0200 Subject: [PATCH 33/61] fixed send cut sets action --- .../src-language-server/stpa/stpa-module.ts | 25 ----- .../src-webview/options/cut-set-registry.ts | 7 +- extension/src/extension.ts | 94 +++++++------------ extension/src/language-extension.ts | 2 +- extension/src/utils.ts | 11 +++ 5 files changed, 48 insertions(+), 91 deletions(-) diff --git a/extension/src-language-server/stpa/stpa-module.ts b/extension/src-language-server/stpa/stpa-module.ts index 0b9bd3a..aa7a86f 100644 --- a/extension/src-language-server/stpa/stpa-module.ts +++ b/extension/src-language-server/stpa/stpa-module.ts @@ -157,28 +157,3 @@ export const StpaSprottySharedModule: Module new DefaultDiagramServerManager(services), }, }; - -/** - * Create the full set of services required by Langium. - * - * First inject the shared services by merging two modules: - * - Langium default shared services - * - Services generated by langium-cli - * - * Then inject the language-specific services by merging three modules: - * - Langium default language-specific services - * - Services generated by langium-cli - * - Services specified in this file - * - * @param context Optional module context with the LSP connection - * @returns An object wrapping the shared services and the language-specific services - */ -export function createStpaServices(context: DefaultSharedModuleContext): { - shared: LangiumSprottySharedServices; - states: StpaServices; -} { - const shared = inject(createDefaultSharedModule(context), StpaGeneratedSharedModule, StpaSprottySharedModule); - const states = inject(createDefaultModule({ shared }), StpaGeneratedModule, STPAModule); - shared.ServiceRegistry.register(states); - return { shared, states }; -} diff --git a/extension/src-webview/options/cut-set-registry.ts b/extension/src-webview/options/cut-set-registry.ts index 22c685e..f9a98ad 100644 --- a/extension/src-webview/options/cut-set-registry.ts +++ b/extension/src-webview/options/cut-set-registry.ts @@ -16,7 +16,7 @@ */ import { injectable } from "inversify"; -import { ICommand } from "sprotty"; +import { ICommand, IActionHandlerInitializer, ActionHandlerRegistry } from "sprotty"; import { Action, UpdateModelAction } from "sprotty-protocol"; import { Registry } from "../base/registry"; import { SendCutSetAction } from "./actions"; @@ -43,11 +43,6 @@ export class CutSetsRegistry extends Registry{ private _options: Map = new Map(); - constructor(){ - super(); - } - - handle(action: Action): void | Action | ICommand{ if(SendCutSetAction.isThisAction(action)){ const dropDownOption = new DropDownMenuOption(); diff --git a/extension/src/extension.ts b/extension/src/extension.ts index d649308..a9e19ed 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -23,11 +23,12 @@ import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } f import { Messenger } from 'vscode-messenger'; import { command } from './constants'; import { StpaLspVscodeExtension } from './language-extension'; -import { createQuickPickForWorkspaceOptions } from './utils'; +import { createOutputChannel, createQuickPickForWorkspaceOptions } from './utils'; import { createSTPAResultMarkdownFile } from './report/md-export'; import { LTLFormula } from './sbm/utils'; import { createSBMs } from './sbm/sbm-generation'; import { StpaResult } from './report/utils'; +import { SendCutSetAction } from './actions'; let languageClient: LanguageClient; @@ -51,9 +52,9 @@ export function activate(context: vscode.ExtensionContext): void { singleton: true, messenger: new Messenger({ ignoreHiddenViews: false }) }, 'pasta'); - registerDefaultCommands(webviewPanelManager, context, { extensionPrefix: 'stpa' }); + registerDefaultCommands(webviewPanelManager, context, { extensionPrefix: 'pasta' }); registerTextEditorSync(webviewPanelManager, context); - registerSTPACommands(webviewPanelManager, context, { extensionPrefix: 'stpa' }); + registerSTPACommands(webviewPanelManager, context, { extensionPrefix: 'pasta' }); } if (diagramMode === 'editor') { @@ -170,66 +171,41 @@ function registerSTPACommands(manager: StpaLspVscodeExtension, context: vscode.E return formulas; } )); - // TODO - //commands for computing and displaying the (minimal) cut sets of the fault tree. - // this.context.subscriptions.push( - // vscode.commands.registerCommand(this.extensionPrefix + '.generate.ftaCutSets', async () =>{ - // const cutSets:string = await this.languageClient.sendRequest('generate/getCutSets'); - - // //Send cut sets to webview to display them in a dropdown menu. - // this.dispatchCutSetsToWebview(cutSets); - // this.createOutputChannel(cutSets, "All cut sets"); - // }) - // ); - // this.context.subscriptions.push( - // vscode.commands.registerCommand(this.extensionPrefix + '.generate.ftaMinimalCutSets', async () =>{ - // const minimalCutSets:string = await this.languageClient.sendRequest('generate/getMinimalCutSets'); - - // this.dispatchCutSetsToWebview(minimalCutSets); - - // this.createOutputChannel(minimalCutSets, "All minimal cut sets"); - // }) - // ); + // commands for computing and displaying the (minimal) cut sets of the fault tree. + context.subscriptions.push( + vscode.commands.registerCommand(options.extensionPrefix + '.generate.ftaCutSets', async () =>{ + const cutSets:string = await languageClient.sendRequest('generate/getCutSets'); + //Send cut sets to webview to display them in a dropdown menu. + dispatchCutSetsToWebview(manager, cutSets); + createOutputChannel(cutSets, "All cut sets"); + }) + ); + context.subscriptions.push( + vscode.commands.registerCommand(options.extensionPrefix + '.generate.ftaMinimalCutSets', async () =>{ + const minimalCutSets:string = await languageClient.sendRequest('generate/getMinimalCutSets'); + dispatchCutSetsToWebview(manager, minimalCutSets); + createOutputChannel(minimalCutSets, "All minimal cut sets"); + }) + ); } -// protected getDiagramType(commandArgs: any[]): string | undefined { -// if (commandArgs.length === 0 -// || commandArgs[0] instanceof vscode.Uri && commandArgs[0].path.endsWith('.stpa')) { -// return 'stpa-diagram'; -// } -// if(commandArgs[0] instanceof vscode.Uri && commandArgs[0].path.endsWith('.fta')){ -// return 'fta-diagram'; -// } -// return undefined; -// } - -// /** -// * Sends the cut sets to webview as a SendCutSetAction so that they can be displayed in a dropdown menu. -// * @param cutSets The (minimal) cut sets of the current Fault Tree. -// */ -// protected dispatchCutSetsToWebview(cutSets:string):void{ -// cutSets = cutSets.substring(cutSets.indexOf("[")); -// cutSets = cutSets.slice(1,-2); -// const cutSetArray = cutSets.split(",\n"); - -// const cutSetDropDownList: { value: any; }[] = []; -// for(const set of cutSetArray){ -// cutSetDropDownList.push({value: set}); -// } -// this.singleton?.dispatch({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); -// } +/** + * Sends the cut sets to webview as a SendCutSetAction so that they can be displayed in a dropdown menu. + * @param cutSets The (minimal) cut sets of the current Fault Tree. + */ +function dispatchCutSetsToWebview(manager: StpaLspVscodeExtension, cutSets:string):void{ + cutSets = cutSets.substring(cutSets.indexOf("[")); + cutSets = cutSets.slice(1,-2); + const cutSetArray = cutSets.split(",\n"); -// /** -// * Creates an output channel with the given name and prints the given cut sets. -// * @param cutSets The cut sets to print. -// * @param channelName The name of the channel. -// */ -// protected createOutputChannel(cutSets:string, channelName:string):void{ -// const outputCutSets = vscode.window.createOutputChannel(channelName); -// outputCutSets.append(cutSets); -// outputCutSets.show(); -// } + const cutSetDropDownList: { value: any; }[] = []; + for(const set of cutSetArray){ + cutSetDropDownList.push({value: set}); + } + // manager.endpoints.find(endpoint => endpoint.diagramIdentifier?.diagramType === 'fta')?.sendAction({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); + manager.endpoints.find(endpoint => endpoint.diagramIdentifier?.uri.endsWith('.fta'))?.sendAction({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); + } function createLanguageClient(context: vscode.ExtensionContext): LanguageClient { const serverModule = context.asAbsolutePath(path.join('pack', 'language-server')); diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index 54b39dd..edabcbf 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -20,7 +20,7 @@ import { createFileUri } from "sprotty-vscode"; import { SprottyDiagramIdentifier } from "sprotty-vscode-protocol"; import { LspWebviewEndpoint, LspWebviewPanelManager, LspWebviewPanelManagerOptions } from "sprotty-vscode/lib/lsp"; import * as vscode from "vscode"; -import { GenerateSVGsAction, SendCutSetAction } from "./actions"; +import { GenerateSVGsAction } from "./actions"; import { ContextTablePanel } from "./context-table-panel"; import { StpaFormattingEditProvider } from "./stpa-formatter"; import { applyTextEdits, collectOptions, createFile } from "./utils"; diff --git a/extension/src/utils.ts b/extension/src/utils.ts index 5560362..6bd7aa8 100644 --- a/extension/src/utils.ts +++ b/extension/src/utils.ts @@ -113,3 +113,14 @@ export class UCA_TYPE { static CONTINUOUS = "continuous-problem"; static UNDEFINED = "undefined"; } + +/** + * Creates an output channel with the given name and prints the given cut sets. + * @param cutSets The cut sets to print. + * @param channelName The name of the channel. + */ +export function createOutputChannel(cutSets:string, channelName:string):void{ + const outputCutSets = vscode.window.createOutputChannel(channelName); + outputCutSets.append(cutSets); + outputCutSets.show(); +} \ No newline at end of file From 6a83dfa35dc3ab6c908d97a0ad1e3273a4d5dedf Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Wed, 20 Sep 2023 13:41:28 +0200 Subject: [PATCH 34/61] structuring (diagram server) --- extension/langium-config.json | 2 +- ...tpa-diagramServer.ts => diagram-server.ts} | 61 +++++++------- .../src-language-server/fta/fta-module.ts | 45 ++--------- .../src-language-server/fta/fta-validator.ts | 4 +- .../fta/synthesis-options.ts | 27 +++++++ extension/src-language-server/module.ts | 80 ++++++++++++++----- .../stpa/diagram/diagram-generator.ts | 2 +- .../stpa/diagram/synthesis-options.ts | 9 +-- .../stpa/message-handler.ts | 2 +- .../src-language-server/stpa/stpa-module.ts | 58 ++------------ .../stpa/stpa-validator.ts | 4 +- .../src-language-server/synthesis-options.ts | 30 +++++++ .../src-webview/options/options-module.ts | 3 - extension/src/extension.ts | 5 +- 14 files changed, 172 insertions(+), 160 deletions(-) rename extension/src-language-server/{stpa/diagram/stpa-diagramServer.ts => diagram-server.ts} (81%) create mode 100644 extension/src-language-server/fta/synthesis-options.ts create mode 100644 extension/src-language-server/synthesis-options.ts diff --git a/extension/langium-config.json b/extension/langium-config.json index da85277..c8c1fe8 100644 --- a/extension/langium-config.json +++ b/extension/langium-config.json @@ -1,5 +1,5 @@ { - "projectName": "Stpa", + "projectName": "Pasta", "languages": [{ "id": "stpa", "grammar": "src-language-server/stpa.langium", diff --git a/extension/src-language-server/stpa/diagram/stpa-diagramServer.ts b/extension/src-language-server/diagram-server.ts similarity index 81% rename from extension/src-language-server/stpa/diagram/stpa-diagramServer.ts rename to extension/src-language-server/diagram-server.ts index d04213a..eee61a7 100644 --- a/extension/src-language-server/stpa/diagram/stpa-diagramServer.ts +++ b/extension/src-language-server/diagram-server.ts @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2022 by + * Copyright 2022-2023 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -25,9 +25,10 @@ import { ResponseAction, } from "sprotty-protocol"; import { Connection } from "vscode-languageserver"; -import { SetSynthesisOptionsAction, UpdateOptionsAction } from "../../options/actions"; -import { DropDownOption } from "../../options/option-models"; -import { GenerateSVGsAction, RequestSvgAction, SvgAction } from "../actions"; +import { SetSynthesisOptionsAction, UpdateOptionsAction } from "./options/actions"; +import { DropDownOption } from "./options/option-models"; +import { GenerateSVGsAction, RequestSvgAction, SvgAction } from "./stpa/actions"; +import { StpaSynthesisOptions, filteringUCAsID } from "./stpa/diagram/synthesis-options"; import { COMPLETE_GRAPH_PATH, CONTROL_STRUCTURE_PATH, @@ -51,11 +52,11 @@ import { setScenarioWithFilteredUCAGraphOptions, setScenarioWithNoUCAGraphOptions, setSystemConstraintGraphOptions, -} from "../result-report/svg-generator"; -import { StpaSynthesisOptions, filteringUCAsID } from "./synthesis-options"; +} from "./stpa/result-report/svg-generator"; +import { SynthesisOptions } from "./synthesis-options"; -export class StpaDiagramServer extends DiagramServer { - protected stpaOptions: StpaSynthesisOptions | undefined; +export class PastaDiagramServer extends DiagramServer { + protected synthesisOptions: SynthesisOptions | undefined; clientId: string; protected connection: Connection | undefined; @@ -64,10 +65,10 @@ export class StpaDiagramServer extends DiagramServer { services: DiagramServices, clientId: string, connection: Connection | undefined, - synthesisOptions?: StpaSynthesisOptions + synthesisOptions?: SynthesisOptions ) { super(dispatch, services); - this.stpaOptions = synthesisOptions; + this.synthesisOptions = synthesisOptions; this.clientId = clientId; this.connection = connection; } @@ -100,59 +101,59 @@ export class StpaDiagramServer extends DiagramServer { * @returns */ async handleGenerateSVGDiagrams(action: GenerateSVGsAction): Promise { - if (this.stpaOptions) { + if (this.synthesisOptions && this.synthesisOptions instanceof StpaSynthesisOptions) { diagramSizes = {}; const setSynthesisOption = { kind: SetSynthesisOptionsAction.KIND, - options: this.stpaOptions.getSynthesisOptions().map((option) => option.synthesisOption), + options: this.synthesisOptions.getSynthesisOptions().map((option) => option.synthesisOption), } as SetSynthesisOptionsAction; // save current option values - saveOptions(this.stpaOptions); + saveOptions(this.synthesisOptions); // control structure svg - setControlStructureOptions(this.stpaOptions); + setControlStructureOptions(this.synthesisOptions); await this.createSVG(setSynthesisOption, action.uri, CONTROL_STRUCTURE_PATH); // hazard graph svg - setHazardGraphOptions(this.stpaOptions); + setHazardGraphOptions(this.synthesisOptions); await this.createSVG(setSynthesisOption, action.uri, HAZARD_PATH); // system constraint graph svg - setSystemConstraintGraphOptions(this.stpaOptions); + setSystemConstraintGraphOptions(this.synthesisOptions); await this.createSVG(setSynthesisOption, action.uri, SYSTEM_CONSTRAINT_PATH); // responsibility graph svg - setResponsibilityGraphOptions(this.stpaOptions); + setResponsibilityGraphOptions(this.synthesisOptions); await this.createSVG(setSynthesisOption, action.uri, RESPONSIBILITY_PATH); // filtered uca graph svg - const filteringUcaOption = this.stpaOptions + const filteringUcaOption = this.synthesisOptions .getSynthesisOptions() .find((option) => option.synthesisOption.id === filteringUCAsID); for (const value of (filteringUcaOption?.synthesisOption as DropDownOption).availableValues) { - setFilteredUcaGraphOptions(this.stpaOptions, value.id); + setFilteredUcaGraphOptions(this.synthesisOptions, value.id); await this.createSVG(setSynthesisOption, action.uri, FILTERED_UCA_PATH(value.id)); } // filtered controller constraint graph svg for (const value of (filteringUcaOption?.synthesisOption as DropDownOption).availableValues) { - setControllerConstraintWithFilteredUcaGraphOptions(this.stpaOptions, value.id); + setControllerConstraintWithFilteredUcaGraphOptions(this.synthesisOptions, value.id); await this.createSVG(setSynthesisOption, action.uri, FILTERED_CONTROLLER_CONSTRAINT_PATH(value.id)); } // filtered scenario graph svg for (const value of (filteringUcaOption?.synthesisOption as DropDownOption).availableValues) { - setScenarioWithFilteredUCAGraphOptions(this.stpaOptions, value.id); + setScenarioWithFilteredUCAGraphOptions(this.synthesisOptions, value.id); await this.createSVG(setSynthesisOption, action.uri, FILTERED_SCENARIO_PATH(value.id)); } // scenario with hazard svg graph - setScenarioWithNoUCAGraphOptions(this.stpaOptions); + setScenarioWithNoUCAGraphOptions(this.synthesisOptions); await this.createSVG(setSynthesisOption, action.uri, SCENARIO_WITH_HAZARDS_PATH); // safety requirement svg graph - setSafetyRequirementGraphOptions(this.stpaOptions); + setSafetyRequirementGraphOptions(this.synthesisOptions); await this.createSVG(setSynthesisOption, action.uri, SAFETY_REQUIREMENT_PATH); // complete graph svg - setRelationshipGraphOptions(this.stpaOptions); + setRelationshipGraphOptions(this.synthesisOptions); await this.createSVG(setSynthesisOption, action.uri, COMPLETE_GRAPH_PATH); // reset options - resetOptions(this.stpaOptions); + resetOptions(this.synthesisOptions); await this.handleSetSynthesisOption(setSynthesisOption); } @@ -180,9 +181,9 @@ export class StpaDiagramServer extends DiagramServer { } protected async handleSetSynthesisOption(action: SetSynthesisOptionsAction): Promise { - if (this.stpaOptions) { + if (this.synthesisOptions) { for (const option of action.options) { - const opt = this.stpaOptions + const opt = this.synthesisOptions .getSynthesisOptions() .find((synOpt) => synOpt.synthesisOption.id === option.id); if (opt) { @@ -193,7 +194,7 @@ export class StpaDiagramServer extends DiagramServer { (opt.synthesisOption as DropDownOption).currentId = option.currentValue; this.dispatch({ kind: UpdateOptionsAction.KIND, - valuedSynthesisOptions: this.stpaOptions.getSynthesisOptions(), + valuedSynthesisOptions: this.synthesisOptions.getSynthesisOptions(), clientId: this.clientId, }); } @@ -217,7 +218,7 @@ export class StpaDiagramServer extends DiagramServer { // ensures the the filterUCA option is correct this.dispatch({ kind: UpdateOptionsAction.KIND, - valuedSynthesisOptions: this.stpaOptions?.getSynthesisOptions() ?? [], + valuedSynthesisOptions: this.synthesisOptions?.getSynthesisOptions() ?? [], clientId: this.clientId, }); } catch (err) { @@ -230,7 +231,7 @@ export class StpaDiagramServer extends DiagramServer { await super.handleRequestModel(action); this.dispatch({ kind: UpdateOptionsAction.KIND, - valuedSynthesisOptions: this.stpaOptions?.getSynthesisOptions() ?? [], + valuedSynthesisOptions: this.synthesisOptions?.getSynthesisOptions() ?? [], clientId: this.clientId, }); } diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index 979078e..38e1616 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -17,15 +17,13 @@ import ElkConstructor from 'elkjs/lib/elk.bundled'; import { Module, PartialLangiumServices } from 'langium'; -import { DefaultDiagramServerManager, DiagramActionNotification, LangiumSprottyServices, LangiumSprottySharedServices, SprottyDiagramServices, SprottySharedServices } from 'langium-sprotty'; +import { LangiumSprottyServices, SprottyDiagramServices } from 'langium-sprotty'; import { DefaultElementFilter, ElkFactory, ElkLayoutEngine, IElementFilter, ILayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; -import { DiagramOptions } from 'sprotty-protocol'; -import { URI } from 'vscode-uri'; -import { StpaDiagramServer } from '../stpa/diagram/stpa-diagramServer'; import { CutSetGenerator } from './fta-cutSet-generator'; import { FtaDiagramGenerator } from './fta-diagram-generator'; import { FtaLayoutConfigurator } from './fta-layout-config'; import { FtaValidationRegistry, FtaValidator } from './fta-validator'; +import { FtaSynthesisOptions } from './synthesis-options'; /** @@ -40,6 +38,9 @@ export type FtaAddedServices = { ElementFilter: IElementFilter, LayoutConfigurator: ILayoutConfigurator; }, + options: { + SynthesisOptions: FtaSynthesisOptions; + }; bdd: { Bdd: CutSetGenerator } @@ -70,40 +71,10 @@ export const FtaModule: Module new DefaultElementFilter, LayoutConfigurator: () => new FtaLayoutConfigurator }, + options: { + SynthesisOptions: () => new FtaSynthesisOptions(), + }, bdd:{ Bdd: () => new CutSetGenerator() } }; - -export const ftaDiagramServerFactory = - (services: LangiumSprottySharedServices): ((clientId: string, options?: DiagramOptions) => StpaDiagramServer) => { - const connection = services.lsp.Connection; - const serviceRegistry = services.ServiceRegistry; - return (clientId, options) => { - const sourceUri = options?.sourceUri; - if (!sourceUri) { - throw new Error("Missing 'sourceUri' option in request."); - } - const language = serviceRegistry.getServices(URI.parse(sourceUri as string)) as FtaServices; - if (!language.diagram) { - throw new Error(`The '${language.LanguageMetaData.languageId}' language does not support diagrams.`); - } - return new StpaDiagramServer(async action => { - connection?.sendNotification(DiagramActionNotification.type, { clientId, action }); - }, - language.diagram, - clientId, - connection); - }; - }; -/** - * instead of the default diagram server the fta-diagram server is used - */ -export const FtaSprottySharedModule: Module = { - diagram: { - diagramServerFactory: ftaDiagramServerFactory, - DiagramServerManager: services => new DefaultDiagramServerManager(services) - } -}; - - diff --git a/extension/src-language-server/fta/fta-validator.ts b/extension/src-language-server/fta/fta-validator.ts index 638f81d..52839f7 100644 --- a/extension/src-language-server/fta/fta-validator.ts +++ b/extension/src-language-server/fta/fta-validator.ts @@ -16,7 +16,7 @@ */ import { ValidationAcceptor, ValidationChecks, ValidationRegistry } from 'langium'; -import { ModelFTA, StpaAstType } from '../generated/ast'; +import { ModelFTA, PastaAstType } from '../generated/ast'; import type { FtaServices } from './fta-module'; /** @@ -26,7 +26,7 @@ export class FtaValidationRegistry extends ValidationRegistry { constructor(services: FtaServices) { super(services); const validator = services.validation.FtaValidator; - const checks: ValidationChecks = { + const checks: ValidationChecks = { ModelFTA: validator.checkModel, }; this.register(checks, validator); diff --git a/extension/src-language-server/fta/synthesis-options.ts b/extension/src-language-server/fta/synthesis-options.ts new file mode 100644 index 0000000..e9bc9f0 --- /dev/null +++ b/extension/src-language-server/fta/synthesis-options.ts @@ -0,0 +1,27 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { SynthesisOptions } from "../synthesis-options"; + +export class FtaSynthesisOptions extends SynthesisOptions { + + constructor() { + super(); + // this.options = []; + } + +} \ No newline at end of file diff --git a/extension/src-language-server/module.ts b/extension/src-language-server/module.ts index 970516d..63a506f 100644 --- a/extension/src-language-server/module.ts +++ b/extension/src-language-server/module.ts @@ -15,12 +15,19 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { createDefaultModule, createDefaultSharedModule, DefaultSharedModuleContext, inject } from 'langium'; -import { LangiumSprottySharedServices } from 'langium-sprotty'; -import { StpaGeneratedModule, StpaGeneratedSharedModule, FtaGeneratedModule} from './generated/module'; -import { StpaServices, StpaSprottySharedModule, STPAModule } from './stpa/stpa-module'; -import{FtaModule, FtaSprottySharedModule, FtaServices} from './fta/fta-module'; - +import { createDefaultModule, createDefaultSharedModule, DefaultSharedModuleContext, inject, Module } from "langium"; +import { + DefaultDiagramServerManager, + DiagramActionNotification, + LangiumSprottySharedServices, + SprottySharedServices +} from "langium-sprotty"; +import { DiagramOptions } from "sprotty-protocol"; +import { URI } from "vscode-uri"; +import { PastaDiagramServer } from "./diagram-server"; +import { FtaModule, FtaServices } from "./fta/fta-module"; +import { FtaGeneratedModule, PastaGeneratedSharedModule, StpaGeneratedModule } from "./generated/module"; +import { STPAModule, StpaServices } from "./stpa/stpa-module"; /** * Create the full set of services required by Langium. @@ -37,24 +44,55 @@ import{FtaModule, FtaSprottySharedModule, FtaServices} from './fta/fta-module'; * @param context Optional module context with the LSP connection * @returns An object wrapping the shared services and the language-specific services */ -export function createServices(context: DefaultSharedModuleContext): { shared: LangiumSprottySharedServices, stpa: StpaServices, fta:FtaServices; } { +export function createServices(context: DefaultSharedModuleContext): { + shared: LangiumSprottySharedServices; + stpa: StpaServices; + fta: FtaServices; +} { const shared = inject( createDefaultSharedModule(context), - StpaGeneratedSharedModule, - StpaSprottySharedModule, - FtaSprottySharedModule - ); - const stpa = inject( - createDefaultModule({ shared }), - StpaGeneratedModule, - STPAModule - ); - const fta = inject( - createDefaultModule({ shared }), - FtaGeneratedModule, - FtaModule + PastaGeneratedSharedModule, + PastaSprottySharedModule ); + const stpa = inject(createDefaultModule({ shared }), StpaGeneratedModule, STPAModule); + const fta = inject(createDefaultModule({ shared }), FtaGeneratedModule, FtaModule); shared.ServiceRegistry.register(stpa); shared.ServiceRegistry.register(fta); - return { shared,stpa, fta}; + return { shared, stpa, fta }; } + +const pastaDiagramServerFactory = ( + services: LangiumSprottySharedServices +): ((clientId: string, options?: DiagramOptions) => PastaDiagramServer) => { + const connection = services.lsp.Connection; + const serviceRegistry = services.ServiceRegistry; + return (clientId, options) => { + const sourceUri = options?.sourceUri; + if (!sourceUri) { + throw new Error("Missing 'sourceUri' option in request."); + } + const language = serviceRegistry.getServices(URI.parse(sourceUri as string)) as (StpaServices | FtaServices); + if (!language.diagram) { + throw new Error(`The '${language.LanguageMetaData.languageId}' language does not support diagrams.`); + } + return new PastaDiagramServer( + async (action) => { + connection?.sendNotification(DiagramActionNotification.type, { clientId, action }); + }, + language.diagram, + clientId, + connection, + language.options.SynthesisOptions + ); + }; +}; + +/** + * instead of the default diagram server the stpa-diagram server is sued + */ +const PastaSprottySharedModule: Module = { + diagram: { + diagramServerFactory: pastaDiagramServerFactory, + DiagramServerManager: (services) => new DefaultDiagramServerManager(services), + }, +}; \ No newline at end of file diff --git a/extension/src-language-server/stpa/diagram/diagram-generator.ts b/extension/src-language-server/stpa/diagram/diagram-generator.ts index eb3aada..01b2e34 100644 --- a/extension/src-language-server/stpa/diagram/diagram-generator.ts +++ b/extension/src-language-server/stpa/diagram/diagram-generator.ts @@ -59,7 +59,7 @@ export class StpaDiagramGenerator extends LangiumDiagramGenerator { constructor(services: StpaServices) { super(services); - this.options = services.options.StpaSynthesisOptions; + this.options = services.options.SynthesisOptions; } /** diff --git a/extension/src-language-server/stpa/diagram/synthesis-options.ts b/extension/src-language-server/stpa/diagram/synthesis-options.ts index 824d037..c398ce4 100644 --- a/extension/src-language-server/stpa/diagram/synthesis-options.ts +++ b/extension/src-language-server/stpa/diagram/synthesis-options.ts @@ -22,6 +22,7 @@ import { TransformationOptionType, ValuedSynthesisOption, } from "../../options/option-models"; +import { SynthesisOptions } from "../../synthesis-options"; const hierarchyID = "hierarchy"; const modelOrderID = "modelOrder"; @@ -397,10 +398,10 @@ export enum showLabelsValue { AUTOMATIC, } -export class StpaSynthesisOptions { - private options: ValuedSynthesisOption[]; +export class StpaSynthesisOptions extends SynthesisOptions { constructor() { + super(); this.options = [ layoutCategoryOption, filterCategoryOption, @@ -423,10 +424,6 @@ export class StpaSynthesisOptions { ]; } - getSynthesisOptions(): ValuedSynthesisOption[] { - return this.options; - } - getModelOrder(): boolean { return this.getOption(modelOrderID)?.currentValue; } diff --git a/extension/src-language-server/stpa/message-handler.ts b/extension/src-language-server/stpa/message-handler.ts index 5044284..7127019 100644 --- a/extension/src-language-server/stpa/message-handler.ts +++ b/extension/src-language-server/stpa/message-handler.ts @@ -19,7 +19,7 @@ import { DocumentState } from "langium"; import { LangiumSprottySharedServices } from "langium-sprotty"; import { TextDocumentContentChangeEvent } from "vscode"; import { Connection, URI } from "vscode-languageserver"; -import { diagramSizes } from "./diagram/stpa-diagramServer"; +import { diagramSizes } from "../diagram-server"; import { generateLTLFormulae } from "./modelChecking/model-checking"; import { createResultData } from "./result-report/result-generator"; import { StpaServices } from "./stpa-module"; diff --git a/extension/src-language-server/stpa/stpa-module.ts b/extension/src-language-server/stpa/stpa-module.ts index aa7a86f..1bbc23d 100644 --- a/extension/src-language-server/stpa/stpa-module.ts +++ b/extension/src-language-server/stpa/stpa-module.ts @@ -17,20 +17,12 @@ import ElkConstructor from "elkjs/lib/elk.bundled"; import { - createDefaultModule, - createDefaultSharedModule, - DefaultSharedModuleContext, - inject, Module, - PartialLangiumServices, + PartialLangiumServices } from "langium"; import { - DefaultDiagramServerManager, - DiagramActionNotification, LangiumSprottyServices, - LangiumSprottySharedServices, - SprottyDiagramServices, - SprottySharedServices, + SprottyDiagramServices } from "langium-sprotty"; import { DefaultElementFilter, @@ -39,15 +31,11 @@ import { IElementFilter, ILayoutConfigurator, } from "sprotty-elk/lib/elk-layout"; -import { DiagramOptions } from "sprotty-protocol"; -import { URI } from "vscode-uri"; -import { StpaGeneratedModule, StpaGeneratedSharedModule } from "../generated/module"; +import { IDEnforcer } from "./ID-enforcer"; import { ContextTableProvider } from "./contextTable/context-dataProvider"; import { StpaDiagramGenerator } from "./diagram/diagram-generator"; import { StpaLayoutConfigurator } from "./diagram/layout-config"; -import { StpaDiagramServer } from "./diagram/stpa-diagramServer"; import { StpaSynthesisOptions } from "./diagram/synthesis-options"; -import { IDEnforcer } from "./ID-enforcer"; import { StpaScopeProvider } from "./stpa-scopeProvider"; import { StpaValidationRegistry, StpaValidator } from "./stpa-validator"; @@ -67,7 +55,7 @@ export type StpaAddedServices = { LayoutConfigurator: ILayoutConfigurator; }; options: { - StpaSynthesisOptions: StpaSynthesisOptions; + SynthesisOptions: StpaSynthesisOptions; }; contextTable: { ContextTableProvider: ContextTableProvider; @@ -112,7 +100,7 @@ export const STPAModule: Module new StpaLayoutConfigurator(), }, options: { - StpaSynthesisOptions: () => new StpaSynthesisOptions(), + SynthesisOptions: () => new StpaSynthesisOptions(), }, contextTable: { ContextTableProvider: (services) => new ContextTableProvider(services), @@ -121,39 +109,3 @@ export const STPAModule: Module new IDEnforcer(services), }, }; - -export const stpaDiagramServerFactory = ( - services: LangiumSprottySharedServices -): ((clientId: string, options?: DiagramOptions) => StpaDiagramServer) => { - const connection = services.lsp.Connection; - const serviceRegistry = services.ServiceRegistry; - return (clientId, options) => { - const sourceUri = options?.sourceUri; - if (!sourceUri) { - throw new Error("Missing 'sourceUri' option in request."); - } - const language = serviceRegistry.getServices(URI.parse(sourceUri as string)) as StpaServices; - if (!language.diagram) { - throw new Error(`The '${language.LanguageMetaData.languageId}' language does not support diagrams.`); - } - return new StpaDiagramServer( - async (action) => { - connection?.sendNotification(DiagramActionNotification.type, { clientId, action }); - }, - language.diagram, - clientId, - connection, - language.options.StpaSynthesisOptions - ); - }; -}; - -/** - * instead of the default diagram server the stpa-diagram server is sued - */ -export const StpaSprottySharedModule: Module = { - diagram: { - diagramServerFactory: stpaDiagramServerFactory, - DiagramServerManager: (services) => new DefaultDiagramServerManager(services), - }, -}; diff --git a/extension/src-language-server/stpa/stpa-validator.ts b/extension/src-language-server/stpa/stpa-validator.ts index 3045c6e..a31aeb5 100644 --- a/extension/src-language-server/stpa/stpa-validator.ts +++ b/extension/src-language-server/stpa/stpa-validator.ts @@ -26,7 +26,7 @@ import { Model, Node, Responsibility, - StpaAstType, + PastaAstType, SystemConstraint, isModel, } from "../generated/ast"; @@ -40,7 +40,7 @@ export class StpaValidationRegistry extends ValidationRegistry { constructor(services: StpaServices) { super(services); const validator = services.validation.StpaValidator; - const checks: ValidationChecks = { + const checks: ValidationChecks = { Model: validator.checkModel, Hazard: validator.checkHazard, SystemConstraint: validator.checkSystemConstraint, diff --git a/extension/src-language-server/synthesis-options.ts b/extension/src-language-server/synthesis-options.ts new file mode 100644 index 0000000..d2ceed8 --- /dev/null +++ b/extension/src-language-server/synthesis-options.ts @@ -0,0 +1,30 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { ValuedSynthesisOption } from "./options/option-models"; + +export class SynthesisOptions { + protected options: ValuedSynthesisOption[]; + + constructor() { + this.options = []; + } + + getSynthesisOptions(): ValuedSynthesisOption[] { + return this.options; + } +} \ No newline at end of file diff --git a/extension/src-webview/options/options-module.ts b/extension/src-webview/options/options-module.ts index 88b1719..dfe258d 100644 --- a/extension/src-webview/options/options-module.ts +++ b/extension/src-webview/options/options-module.ts @@ -26,7 +26,6 @@ import { OptionsPanel } from "./options-panel"; import { OptionsRegistry } from "./options-registry"; import { OptionsRenderer } from "./options-renderer"; import { RenderOptionsRegistry } from "./render-options-registry"; -// import { VsCodeApi } from "sprotty-vscode-webview/lib/services"; /** Module that configures option related panels and registries. */ export const optionsModule = new ContainerModule((bind, _, isBound) => { @@ -39,8 +38,6 @@ export const optionsModule = new ContainerModule((bind, _, isBound) => { bind(CutSetPanel).toSelf().inSingletonScope(); bind(DISymbol.SidebarPanel).toService(CutSetPanel); - // bind(VsCodeApi); - bind(DISymbol.OptionsRenderer).to(OptionsRenderer); bind(DISymbol.OptionsRegistry).to(OptionsRegistry).inSingletonScope(); bind(TYPES.IActionHandlerInitializer).toService(DISymbol.OptionsRegistry); diff --git a/extension/src/extension.ts b/extension/src/extension.ts index a9e19ed..cb7eb70 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -46,7 +46,6 @@ export function activate(context: vscode.ExtensionContext): void { // Set up webview panel manager for freestyle webviews const webviewPanelManager = new StpaLspVscodeExtension({ extensionUri: context.extensionUri, - defaultDiagramType: 'stpa', languageClient, supportedFileExtensions: ['.stpa', '.fta'], singleton: true, @@ -203,8 +202,8 @@ function dispatchCutSetsToWebview(manager: StpaLspVscodeExtension, cutSets:strin for(const set of cutSetArray){ cutSetDropDownList.push({value: set}); } - // manager.endpoints.find(endpoint => endpoint.diagramIdentifier?.diagramType === 'fta')?.sendAction({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); - manager.endpoints.find(endpoint => endpoint.diagramIdentifier?.uri.endsWith('.fta'))?.sendAction({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); + manager.endpoints.find(endpoint => endpoint.diagramIdentifier?.diagramType === 'fta')?.sendAction({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); + // manager.endpoints.find(endpoint => endpoint.diagramIdentifier?.uri.endsWith('.fta'))?.sendAction({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); } function createLanguageClient(context: vscode.ExtensionContext): LanguageClient { From 4c68201505db7d0ef53fc2ca81031db8ef10b8dd Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Wed, 20 Sep 2023 14:32:48 +0200 Subject: [PATCH 35/61] restructured Cut Set Generator (WIP) --- .../fta/fta-cutSet-generator.ts | 50 ++++++------------- extension/src/extension.ts | 4 +- 2 files changed, 17 insertions(+), 37 deletions(-) diff --git a/extension/src-language-server/fta/fta-cutSet-generator.ts b/extension/src-language-server/fta/fta-cutSet-generator.ts index 0f0d33d..6e71959 100644 --- a/extension/src-language-server/fta/fta-cutSet-generator.ts +++ b/extension/src-language-server/fta/fta-cutSet-generator.ts @@ -17,7 +17,7 @@ import { AstNode } from 'langium'; import { IdCache } from 'langium-sprotty'; -import { isAND, isComponent, isCondition, isGate, isInhibitGate, isKNGate, isOR, isTopEvent } from '../generated/ast'; +import { TopEvent, isAND, isComponent, isCondition, isGate, isInhibitGate, isKNGate, isOR, isTopEvent } from '../generated/ast'; export class CutSetGenerator{ @@ -53,7 +53,6 @@ export class CutSetGenerator{ return false; } } - return true; } @@ -71,17 +70,18 @@ export class CutSetGenerator{ //In the evaluation we check if the child has children too and do the same recursively until the children are components. //Depending on the type of the node process the results of the children differently. - //When there is no gate, return the component - const startingNode = this.getChildOfTopEvent(allNodes); - if(isComponent(startingNode)){ - return [[startingNode]]; + const startNode = this.getChildOfTopEvent(allNodes); + if (startNode) { + if(isComponent(startNode)){ + return [[startNode]]; + } + //Evaluate the child of the top event and recursively the entire Fault Tree. + const cutSets = this.evaluate(startNode, allNodes ,idCache); + return cutSets; + } else { + return []; } - //Evaluate the child of the top event and recursively the entire Fault Tree. - const cutSets = this.evaluate(startingNode, allNodes ,idCache); - - return cutSets; - } /** @@ -260,21 +260,11 @@ export class CutSetGenerator{ * @param allNodes All nodes in the graph. * @returns the child of the topevent. */ - getChildOfTopEvent(allNodes:AstNode[]): AstNode{ - let child: AstNode = {} as AstNode; - - for(const node of allNodes){ - if(isTopEvent(node)){ - for(const ref of node.children){ - if(ref?.ref){ - // There is always only one child of the top event. - child = ref.ref; - } - } - } + getChildOfTopEvent(allNodes:AstNode[]): AstNode | undefined { + const topEventChildren = (allNodes.find(node => isTopEvent(node)) as TopEvent).children; + if (topEventChildren.length !== 0) { + return topEventChildren[0].ref; } - return child; - } /** @@ -286,15 +276,12 @@ export class CutSetGenerator{ */ concatAllLists(a:AstNode[][], b:AstNode[][], idCache:IdCache):AstNode[][]{ const result: AstNode[][] = []; - - if(a.length === 0){ return b; } if(b.length === 0){ return a; } - for (const innerA of a) { for (const innerB of b) { //Add only unique sets @@ -303,13 +290,9 @@ export class CutSetGenerator{ if(this.indexOfArray(newSet, result, idCache) === -1){ result.push(newSet); } - - } } - return result; - } /** @@ -330,10 +313,7 @@ export class CutSetGenerator{ }; const sortA = a.sort(sort); const sortB = b.sort(sort); - - return a.length === b.length && sortA.every((e,i) => e === sortB[i]); - } /** diff --git a/extension/src/extension.ts b/extension/src/extension.ts index cb7eb70..693fa31 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -177,14 +177,14 @@ function registerSTPACommands(manager: StpaLspVscodeExtension, context: vscode.E const cutSets:string = await languageClient.sendRequest('generate/getCutSets'); //Send cut sets to webview to display them in a dropdown menu. dispatchCutSetsToWebview(manager, cutSets); - createOutputChannel(cutSets, "All cut sets"); + createOutputChannel(cutSets, "FTA Cut Sets"); }) ); context.subscriptions.push( vscode.commands.registerCommand(options.extensionPrefix + '.generate.ftaMinimalCutSets', async () =>{ const minimalCutSets:string = await languageClient.sendRequest('generate/getMinimalCutSets'); dispatchCutSetsToWebview(manager, minimalCutSets); - createOutputChannel(minimalCutSets, "All minimal cut sets"); + createOutputChannel(minimalCutSets, "FTA Cut Sets"); }) ); } From d166423c58c5a3a0b4bf19723640f245d3835014 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Wed, 20 Sep 2023 14:37:07 +0200 Subject: [PATCH 36/61] formatting --- .../fta/fta-cutSet-generator.ts | 202 +++++++++--------- .../fta/fta-diagram-generator.ts | 134 ++++++------ .../fta/fta-layout-config.ts | 28 ++- .../fta/fta-message-handler.ts | 32 ++- .../src-language-server/fta/fta-model.ts | 12 +- .../src-language-server/fta/fta-module.ts | 64 +++--- .../src-language-server/fta/fta-utils.ts | 104 ++++----- .../src-language-server/fta/fta-validator.ts | 31 ++- .../fta/synthesis-options.ts | 4 +- extension/src-webview/fta-model.ts | 39 ++-- extension/src-webview/fta-views.tsx | 56 +++-- .../src-webview/options/cut-set-panel.tsx | 15 +- .../src-webview/options/cut-set-registry.ts | 57 +++-- 13 files changed, 398 insertions(+), 380 deletions(-) diff --git a/extension/src-language-server/fta/fta-cutSet-generator.ts b/extension/src-language-server/fta/fta-cutSet-generator.ts index 6e71959..7cc1ea6 100644 --- a/extension/src-language-server/fta/fta-cutSet-generator.ts +++ b/extension/src-language-server/fta/fta-cutSet-generator.ts @@ -15,26 +15,34 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { AstNode } from 'langium'; -import { IdCache } from 'langium-sprotty'; -import { TopEvent, isAND, isComponent, isCondition, isGate, isInhibitGate, isKNGate, isOR, isTopEvent } from '../generated/ast'; - - -export class CutSetGenerator{ - +import { AstNode } from "langium"; +import { IdCache } from "langium-sprotty"; +import { + TopEvent, + isAND, + isComponent, + isCondition, + isGate, + isInhibitGate, + isKNGate, + isOR, + isTopEvent, +} from "../generated/ast"; + +export class CutSetGenerator { /** * Takes the Fault Tree and returns a two-dimensional array of AstNodes where every inner list resembles a minimal cut set. * @param allNodes All Nodes in the graph. * @param idCache The idCache of the generator context from the current graph. * @returns A list of lists that that contains every minimal cut set of the given Fault Tree. */ - determineMinimalCutSet(allNodes:AstNode[], idCache:IdCache):AstNode[][]{ + determineMinimalCutSet(allNodes: AstNode[], idCache: IdCache): AstNode[][] { const bdd = this.generateCutSets(allNodes, idCache); //Cut sets are minimal if, when any basic event is removed from the set, the remaining events collectively are no longer a cut set. //Check every innerList //If inner list contains another array from the bdd array, remove innerList because it cant be a minimal cut set - const minimalCutSet = bdd.filter(innerList => { + const minimalCutSet = bdd.filter((innerList) => { return this.checkIfMinimalCutSet(innerList, bdd); //if this condition is true then the innerList is a minimal cut set }); @@ -47,9 +55,9 @@ export class CutSetGenerator{ * @param bdd All Cut Sets of the Fault Tree * @returns True if the given list is a minimal cut set or false if is not. */ - checkIfMinimalCutSet(innerList:AstNode[], bdd:AstNode[][]):boolean{ - for(const list of bdd){ - if(list.every(e=>innerList.includes(e)) && innerList !== list){ + checkIfMinimalCutSet(innerList: AstNode[], bdd: AstNode[][]): boolean { + for (const list of bdd) { + if (list.every((e) => innerList.includes(e)) && innerList !== list) { return false; } } @@ -62,7 +70,7 @@ export class CutSetGenerator{ * @param idCache The idCache of the generator context from the current graph. * @returns A list of lists that that contains every cut set of the given Fault Tree. */ - generateCutSets(allNodes:AstNode[], idCache:IdCache):AstNode[][]{ + generateCutSets(allNodes: AstNode[], idCache: IdCache): AstNode[][] { //Idea: //Start from the top event. //Get the only child of top event (will always be only one) as our starting node. @@ -73,11 +81,11 @@ export class CutSetGenerator{ //When there is no gate, return the component const startNode = this.getChildOfTopEvent(allNodes); if (startNode) { - if(isComponent(startNode)){ + if (isComponent(startNode)) { return [[startNode]]; } //Evaluate the child of the top event and recursively the entire Fault Tree. - const cutSets = this.evaluate(startNode, allNodes ,idCache); + const cutSets = this.evaluate(startNode, allNodes, idCache); return cutSets; } else { return []; @@ -91,67 +99,70 @@ export class CutSetGenerator{ * @param idCache The idCache of the generator context from the current graph. * @returns A list of lists that is the result of evaluating the given node. */ - evaluate(node:AstNode, allNodes: AstNode[], idCache:IdCache): AstNode[][]{ - let result:AstNode[][] = []; + evaluate(node: AstNode, allNodes: AstNode[], idCache: IdCache): AstNode[][] { + let result: AstNode[][] = []; // we start with the top-most gate(child of topevent) and get all its children. const children = this.getAllChildrenOfNode(node); - if(children.length === 0){return result;}; + if (children.length === 0) { + return result; + } //if the node is an and/inhibit-gate we want to evaluate all children and concatenate all inner lists of one child with another. - if(isGate(node) && (isAND(node.type) || isInhibitGate(node.type))){ - for(const child of children){ - if(isComponent(child) || isCondition(child)){ + if (isGate(node) && (isAND(node.type) || isInhibitGate(node.type))) { + for (const child of children) { + if (isComponent(child) || isCondition(child)) { result = this.concatAllLists([[child]], result, idCache); - }else{ + } else { result = this.concatAllLists(this.evaluate(child, allNodes, idCache), result, idCache); } } - //if the node is an or-gate we want to evaluate all children and add every single inner list to the result. - }else if(isGate(node) && isOR(node.type)){ - for(const child of children){ - if(isComponent(child)){ + //if the node is an or-gate we want to evaluate all children and add every single inner list to the result. + } else if (isGate(node) && isOR(node.type)) { + for (const child of children) { + if (isComponent(child)) { const orList = [child]; result.push(orList); - }else{ + } else { //push every inner list of the child gate. - for(const list of this.evaluate(child, allNodes, idCache)){ + for (const list of this.evaluate(child, allNodes, idCache)) { result.push(list); } } } - - - //if the node is a kN-gate we want to get every combinations of the children with length k and after that evaluate the gates in the list. - }else if(isGate(node) && isKNGate(node.type)){ + //if the node is a kN-gate we want to get every combinations of the children with length k and after that evaluate the gates in the list. + } else if (isGate(node) && isKNGate(node.type)) { const k = node.type.k as number; const n = node.type.children.length as number; - + //Example: With Children:[M1,M2,G1] and k=2 -> [[M1,M2],[M1,G1],[M2,G1]] . - const combinations:AstNode[][]=[]; - for(let i = k; i<=n; i++){ - for(const comb of this.getAllCombinations(children, i)){ + const combinations: AstNode[][] = []; + for (let i = k; i <= n; i++) { + for (const comb of this.getAllCombinations(children, i)) { combinations.push(comb); } } //Now we want to evaluate G1 from the example above (e.g evaluation(G1) = [[C]]). //Our result list should look like this -> [[M1,M2], [M1,C], [M2,C]]. - for(const comb of combinations){ - if(comb.some(e => isGate(e) && (isAND(e.type) || isInhibitGate(e.type) || isOR(e.type) || isKNGate(e.type)))){ + for (const comb of combinations) { + if ( + comb.some( + (e) => isGate(e) && (isAND(e.type) || isInhibitGate(e.type) || isOR(e.type) || isKNGate(e.type)) + ) + ) { const evaluatedLists = this.evaluateGateInCombinationList(comb, allNodes, idCache); - for(const list of evaluatedLists){ + for (const list of evaluatedLists) { result.push(list); } - }else{ + } else { result.push(comb); } } } return result; - } /** @@ -159,47 +170,47 @@ export class CutSetGenerator{ * @param innerList The list we want to evaluate. * @param allNodes All Nodes in the graph. * @param idCache The idCache of the generator context from the current graph. - * @returns A list of lists that is the result of inserting the evaluation of the gates in the given list. + * @returns A list of lists that is the result of inserting the evaluation of the gates in the given list. */ - evaluateGateInCombinationList(innerList: AstNode[], allNodes:AstNode[], idCache:IdCache):AstNode[][]{ - - let result:AstNode[][] = []; - const restList:AstNode[] = innerList; + evaluateGateInCombinationList(innerList: AstNode[], allNodes: AstNode[], idCache: IdCache): AstNode[][] { + let result: AstNode[][] = []; + const restList: AstNode[] = innerList; - for(let i = 0; i nodes.length || k <= 0) { return []; @@ -207,46 +218,50 @@ export class CutSetGenerator{ if (k === nodes.length) { return [nodes]; } - if(k===1){ - for(let i = 0; i isTopEvent(node)) as TopEvent).children; + getChildOfTopEvent(allNodes: AstNode[]): AstNode | undefined { + const topEventChildren = (allNodes.find((node) => isTopEvent(node)) as TopEvent).children; if (topEventChildren.length !== 0) { return topEventChildren[0].ref; } @@ -274,20 +289,20 @@ export class CutSetGenerator{ * @param idCache The idCache of the generator context from the current graph. * @returns a two-dimensional array of type AstNode where every innerList of both arrays is concatenated. */ - concatAllLists(a:AstNode[][], b:AstNode[][], idCache:IdCache):AstNode[][]{ + concatAllLists(a: AstNode[][], b: AstNode[][], idCache: IdCache): AstNode[][] { const result: AstNode[][] = []; - if(a.length === 0){ + if (a.length === 0) { return b; } - if(b.length === 0){ + if (b.length === 0) { return a; } for (const innerA of a) { for (const innerB of b) { //Add only unique sets let newSet = innerA.concat(innerB); - newSet = newSet.filter((e,i) => newSet.indexOf(e) === i); - if(this.indexOfArray(newSet, result, idCache) === -1){ + newSet = newSet.filter((e, i) => newSet.indexOf(e) === i); + if (this.indexOfArray(newSet, result, idCache) === -1) { result.push(newSet); } } @@ -302,18 +317,18 @@ export class CutSetGenerator{ * @param idCache The idCache of the generator context from the current graph. * @returns True if they are equal and false if not. */ - arrayEquals(a:AstNode[], b:AstNode[], idCache:IdCache):boolean{ - const sort = (x:AstNode, y:AstNode):number => { + arrayEquals(a: AstNode[], b: AstNode[], idCache: IdCache): boolean { + const sort = (x: AstNode, y: AstNode): number => { const idX = idCache.getId(x); const idY = idCache.getId(y); - if(idX && idY){ + if (idX && idY) { return idX > idY ? -1 : 1; } return 0; }; const sortA = a.sort(sort); const sortB = b.sort(sort); - return a.length === b.length && sortA.every((e,i) => e === sortB[i]); + return a.length === b.length && sortA.every((e, i) => e === sortB[i]); } /** @@ -323,22 +338,17 @@ export class CutSetGenerator{ * @param idCache The idCache of the generator context from the current graph. * @returns the index of the list. */ - indexOfArray(a:AstNode[], b:AstNode[][], idCache:IdCache):number{ + indexOfArray(a: AstNode[], b: AstNode[][], idCache: IdCache): number { let i = 0; - for(const list of b){ - if(this.arrayEquals(a, list, idCache)){ + for (const list of b) { + if (this.arrayEquals(a, list, idCache)) { break; } i++; } - if(i >= b.length){ + if (i >= b.length) { return -1; } return i; } - } - - - - diff --git a/extension/src-language-server/fta/fta-diagram-generator.ts b/extension/src-language-server/fta/fta-diagram-generator.ts index 229cd51..cab8edc 100644 --- a/extension/src-language-server/fta/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/fta-diagram-generator.ts @@ -15,21 +15,29 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { AstNode } from 'langium'; -import { GeneratorContext, IdCache, LangiumDiagramGenerator } from 'langium-sprotty'; -import { SLabel, SModelElement, SModelRoot } from 'sprotty-protocol'; -import { Component, Condition, Gate, ModelFTA, TopEvent, isComponent, isCondition, isGate, isKNGate } from '../generated/ast'; -import { FTAEdge, FTANode } from './fta-interfaces'; -import { FTA_EDGE_TYPE, FTA_NODE_TYPE, TREE_TYPE } from './fta-model'; -import { FtaServices } from './fta-module'; -import { getAllGateTypes, getFTNodeType, getTargets } from './fta-utils'; - - -export class FtaDiagramGenerator extends LangiumDiagramGenerator{ - - allNodes:AstNode[]; +import { AstNode } from "langium"; +import { GeneratorContext, IdCache, LangiumDiagramGenerator } from "langium-sprotty"; +import { SLabel, SModelElement, SModelRoot } from "sprotty-protocol"; +import { + Component, + Condition, + Gate, + ModelFTA, + TopEvent, + isComponent, + isCondition, + isGate, + isKNGate, +} from "../generated/ast"; +import { FTAEdge, FTANode } from "./fta-interfaces"; +import { FTA_EDGE_TYPE, FTA_NODE_TYPE, TREE_TYPE } from "./fta-model"; +import { FtaServices } from "./fta-module"; +import { getAllGateTypes, getFTNodeType, getTargets } from "./fta-utils"; + +export class FtaDiagramGenerator extends LangiumDiagramGenerator { + allNodes: AstNode[]; idCache: IdCache; - constructor(services: FtaServices){ + constructor(services: FtaServices) { super(services); } @@ -43,8 +51,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ const model: ModelFTA = document.parseResult.value; //set filter for later maybe - - let ftaChildren: SModelElement[] = model.components?.map(comps => this.generateFTANode(comps, args)); + let ftaChildren: SModelElement[] = model.components?.map((comps) => this.generateFTANode(comps, args)); //returns a Map with the gate types as the key and all instances of that type as the value. const allGates: Map = getAllGateTypes(model.gates); @@ -52,62 +59,58 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ //first create the ftaNode for the topevent, conditions and all gates ftaChildren = ftaChildren.concat([ this.generateFTANode(model.topEvent, args), - ...model.conditions?.map(cond => this.generateFTANode(cond,args)).flat(1) + ...model.conditions?.map((cond) => this.generateFTANode(cond, args)).flat(1), ]); - - allGates.forEach((value:AstNode[]) => { + + allGates.forEach((value: AstNode[]) => { ftaChildren = ftaChildren.concat([ - ...value?.map(gates => this.generateFTANode(gates as Gate,args)).flat(1), + ...value?.map((gates) => this.generateFTANode(gates as Gate, args)).flat(1), ]); }); //after that create the edges of the gates and the top event - allGates.forEach((value:AstNode[]) => { + allGates.forEach((value: AstNode[]) => { ftaChildren = ftaChildren.concat([ - ...value?.map(gates => this.generateEdgesForFTANode(gates,args)).flat(1), + ...value?.map((gates) => this.generateEdgesForFTANode(gates, args)).flat(1), ]); }); - ftaChildren = ftaChildren.concat([...this.generateEdgesForFTANode(model.topEvent, args),]); - - + ftaChildren = ftaChildren.concat([...this.generateEdgesForFTANode(model.topEvent, args)]); this.allNodes = model.components; this.allNodes = this.allNodes.concat(model.topEvent, ...model.conditions); - allGates.forEach((value:AstNode[]) => { + allGates.forEach((value: AstNode[]) => { this.allNodes = this.allNodes.concat(...value); }); this.idCache = args.idCache; - - + return { - type: 'graph', - id: 'root', + type: "graph", + id: "root", children: [ { type: TREE_TYPE, - id: 'faultTree', - children: ftaChildren - } - ] + id: "faultTree", + children: ftaChildren, + }, + ], }; - } - + /** * Getter method for every FTANode in the Fault Tree. * @returns every FTANode in the Fault Tree. */ - public getNodes():AstNode[]{ + public getNodes(): AstNode[] { return this.allNodes; } - + /** * Getter method for the idCache to get the ids for every node. * @returns the idCache of generator context. */ - public getCache():IdCache{ + public getCache(): IdCache { return this.idCache; } @@ -127,12 +130,11 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ const targetId = idCache.getId(target); const edgeId = idCache.uniqueId(`${sourceId}:-:${targetId}`, undefined); if (sourceId && targetId) { - const e = this.generateFTAEdge(edgeId, sourceId, targetId, '', args); + const e = this.generateFTAEdge(edgeId, sourceId, targetId, "", args); elements.push(e); } } return elements; - } /** @@ -144,15 +146,21 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ * @param param4 GeneratorContext of the FTA model. * @returns an FTAEdge. */ - private generateFTAEdge(edgeId: string, sourceId: string, targetId: string, label: string, { idCache }: GeneratorContext): FTAEdge { + private generateFTAEdge( + edgeId: string, + sourceId: string, + targetId: string, + label: string, + { idCache }: GeneratorContext + ): FTAEdge { let children: SModelElement[] = []; - if (label !== '') { + if (label !== "") { children = [ { - type: 'label:xref', - id: idCache.uniqueId(edgeId + '.label'), - text: label - } + type: "label:xref", + id: idCache.uniqueId(edgeId + ".label"), + text: label, + }, ]; } return { @@ -173,24 +181,23 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ */ private generateFTANode(node: TopEvent | Gate | Component | Condition, args: GeneratorContext): FTANode { const idCache = args.idCache; - + const nodeId = idCache.uniqueId(node.name, node); const children: SModelElement[] = [ { - type: 'label', - id: idCache.uniqueId(nodeId + '.label'), - text: node.name - } + type: "label", + id: idCache.uniqueId(nodeId + ".label"), + text: node.name, + }, ]; let desc = ""; - if(isComponent(node) || isCondition(node)){ + if (isComponent(node) || isCondition(node)) { desc = node.description; } - - if(isGate(node) && isKNGate(node.type)){ + if (isGate(node) && isKNGate(node.type)) { return { type: FTA_NODE_TYPE, id: nodeId, @@ -200,15 +207,15 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ highlight: true, k: node.type.k, n: node.type.children.length, - layout: 'stack', + layout: "stack", layoutOptions: { paddingTop: 10.0, paddingBottom: 10.0, paddngLeft: 10.0, - paddingRight: 10.0 - } + paddingRight: 10.0, + }, }; - }else{ + } else { return { type: FTA_NODE_TYPE, id: nodeId, @@ -216,15 +223,14 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator{ description: desc, children: children, highlight: true, - layout: 'stack', + layout: "stack", layoutOptions: { paddingTop: 10.0, paddingBottom: 10.0, paddngLeft: 10.0, - paddingRight: 10.0 - } + paddingRight: 10.0, + }, }; } - } -} \ No newline at end of file +} diff --git a/extension/src-language-server/fta/fta-layout-config.ts b/extension/src-language-server/fta/fta-layout-config.ts index d4138c6..83ce3fc 100644 --- a/extension/src-language-server/fta/fta-layout-config.ts +++ b/extension/src-language-server/fta/fta-layout-config.ts @@ -15,30 +15,28 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { LayoutOptions } from 'elkjs'; -import { DefaultLayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; -import { SGraph, SModelIndex, SNode } from 'sprotty-protocol'; +import { LayoutOptions } from "elkjs"; +import { DefaultLayoutConfigurator } from "sprotty-elk/lib/elk-layout"; +import { SGraph, SModelIndex, SNode } from "sprotty-protocol"; export class FtaLayoutConfigurator extends DefaultLayoutConfigurator { - protected graphOptions(sgraph: SGraph, index: SModelIndex): LayoutOptions { //options for the entire graph. return { - 'org.eclipse.elk.spacing.nodeNode': '30.0', - 'org.eclipse.elk.direction': 'DOWN', + "org.eclipse.elk.spacing.nodeNode": "30.0", + "org.eclipse.elk.direction": "DOWN", }; } protected nodeOptions(snode: SNode, index: SModelIndex): LayoutOptions | undefined { - //options for the nodes. + //options for the nodes. return { - 'org.eclipse.elk.nodeLabels.placement': "INSIDE V_CENTER H_CENTER", - + "org.eclipse.elk.nodeLabels.placement": "INSIDE V_CENTER H_CENTER", + //'org.eclipse.elk.nodeSize.constraints': 'NODE_LABELS', - 'org.eclipse.elk.direction' : 'DOWN', - 'org.eclipse.elk.layered.nodePlacement.strategy': 'NETWORK_SIMPLEX', - - }; - } -} \ No newline at end of file + "org.eclipse.elk.direction": "DOWN", + "org.eclipse.elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", + }; + } +} diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index 18c6fd8..a31ff6d 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -17,29 +17,26 @@ import { Connection } from "vscode-languageserver"; import { FtaDiagramGenerator } from "./fta-diagram-generator"; -import { FtaServices } from './fta-module'; -import { cutSetToString, minimalCutSetToString } from './fta-utils'; - +import { FtaServices } from "./fta-module"; +import { cutSetToString, minimalCutSetToString } from "./fta-utils"; /** * Adds handlers for notifications regarding fta. - * @param connection - * @param ftaServices + * @param connection + * @param ftaServices */ export function addFTANotificationHandler(connection: Connection, ftaServices: FtaServices): void { addCutSetsHandler(connection, ftaServices); - } - /** * Adds handlers for requests regarding the cut sets. - * @param connection - * @param ftaServices + * @param connection + * @param ftaServices */ -function addCutSetsHandler(connection: Connection, ftaServices: FtaServices):void{ - connection.onRequest('generate/getCutSets', () =>{ - const diagramGenerator = (ftaServices.diagram.DiagramGenerator) as FtaDiagramGenerator; +function addCutSetsHandler(connection: Connection, ftaServices: FtaServices): void { + connection.onRequest("generate/getCutSets", () => { + const diagramGenerator = ftaServices.diagram.DiagramGenerator as FtaDiagramGenerator; const nodes = diagramGenerator.getNodes(); const idCache = diagramGenerator.getCache(); @@ -47,17 +44,16 @@ function addCutSetsHandler(connection: Connection, ftaServices: FtaServices):voi const cutSetsToString = cutSetToString(cutSets, idCache); return cutSetsToString; - }); + }); - connection.onRequest('generate/getMinimalCutSets', () =>{ - const diagramGenerator = (ftaServices.diagram.DiagramGenerator) as FtaDiagramGenerator; + connection.onRequest("generate/getMinimalCutSets", () => { + const diagramGenerator = ftaServices.diagram.DiagramGenerator as FtaDiagramGenerator; const nodes = diagramGenerator.getNodes(); const idCache = diagramGenerator.getCache(); - + const minimalCutSets = ftaServices.bdd.Bdd.determineMinimalCutSet(nodes, idCache); const minCutSetToString = minimalCutSetToString(minimalCutSets, idCache); - + return minCutSetToString; }); } - diff --git a/extension/src-language-server/fta/fta-model.ts b/extension/src-language-server/fta/fta-model.ts index d84d524..66ec84a 100644 --- a/extension/src-language-server/fta/fta-model.ts +++ b/extension/src-language-server/fta/fta-model.ts @@ -1,4 +1,3 @@ - /* * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient * @@ -17,10 +16,9 @@ */ //diagram elements -export const FTA_NODE_TYPE = 'node:fta'; -export const FTA_EDGE_TYPE = 'edge:fta'; -export const TREE_TYPE = 'node:tree'; - +export const FTA_NODE_TYPE = "node:fta"; +export const FTA_EDGE_TYPE = "edge:fta"; +export const TREE_TYPE = "node:tree"; /** * The different types of nodes of FTA. @@ -33,5 +31,5 @@ export enum FTNodeType { OR, KN, INHIBIT, - UNDEFINED -} \ No newline at end of file + UNDEFINED, +} diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index 38e1616..c5895fc 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -15,16 +15,21 @@ * SPDX-License-Identifier: EPL-2.0 */ -import ElkConstructor from 'elkjs/lib/elk.bundled'; -import { Module, PartialLangiumServices } from 'langium'; -import { LangiumSprottyServices, SprottyDiagramServices } from 'langium-sprotty'; -import { DefaultElementFilter, ElkFactory, ElkLayoutEngine, IElementFilter, ILayoutConfigurator } from 'sprotty-elk/lib/elk-layout'; -import { CutSetGenerator } from './fta-cutSet-generator'; -import { FtaDiagramGenerator } from './fta-diagram-generator'; -import { FtaLayoutConfigurator } from './fta-layout-config'; -import { FtaValidationRegistry, FtaValidator } from './fta-validator'; -import { FtaSynthesisOptions } from './synthesis-options'; - +import ElkConstructor from "elkjs/lib/elk.bundled"; +import { Module, PartialLangiumServices } from "langium"; +import { LangiumSprottyServices, SprottyDiagramServices } from "langium-sprotty"; +import { + DefaultElementFilter, + ElkFactory, + ElkLayoutEngine, + IElementFilter, + ILayoutConfigurator, +} from "sprotty-elk/lib/elk-layout"; +import { CutSetGenerator } from "./fta-cutSet-generator"; +import { FtaDiagramGenerator } from "./fta-diagram-generator"; +import { FtaLayoutConfigurator } from "./fta-layout-config"; +import { FtaValidationRegistry, FtaValidator } from "./fta-validator"; +import { FtaSynthesisOptions } from "./synthesis-options"; /** * Declaration of custom services - add your own service classes here. @@ -32,18 +37,18 @@ import { FtaSynthesisOptions } from './synthesis-options'; export type FtaAddedServices = { validation: { FtaValidator: FtaValidator; - }, + }; layout: { - ElkFactory: ElkFactory, - ElementFilter: IElementFilter, + ElkFactory: ElkFactory; + ElementFilter: IElementFilter; LayoutConfigurator: ILayoutConfigurator; - }, + }; options: { SynthesisOptions: FtaSynthesisOptions; }; bdd: { - Bdd: CutSetGenerator - } + Bdd: CutSetGenerator; + }; }; /** @@ -57,24 +62,29 @@ export type FtaServices = LangiumSprottyServices & FtaAddedServices; * declared custom services. The Langium defaults can be partially specified to override only * selected services, while the custom services must be fully specified. */ -export const FtaModule: Module = { +export const FtaModule: Module = { diagram: { - DiagramGenerator: services => new FtaDiagramGenerator(services), - ModelLayoutEngine: services => new ElkLayoutEngine(services.layout.ElkFactory, services.layout.ElementFilter, services.layout.LayoutConfigurator) as any + DiagramGenerator: (services) => new FtaDiagramGenerator(services), + ModelLayoutEngine: (services) => + new ElkLayoutEngine( + services.layout.ElkFactory, + services.layout.ElementFilter, + services.layout.LayoutConfigurator + ) as any, }, validation: { - ValidationRegistry: services => new FtaValidationRegistry(services), - FtaValidator: () => new FtaValidator() + ValidationRegistry: (services) => new FtaValidationRegistry(services), + FtaValidator: () => new FtaValidator(), }, layout: { - ElkFactory: () => () => new ElkConstructor({ algorithms: ['layered'] }), - ElementFilter: () => new DefaultElementFilter, - LayoutConfigurator: () => new FtaLayoutConfigurator + ElkFactory: () => () => new ElkConstructor({ algorithms: ["layered"] }), + ElementFilter: () => new DefaultElementFilter(), + LayoutConfigurator: () => new FtaLayoutConfigurator(), }, options: { SynthesisOptions: () => new FtaSynthesisOptions(), }, - bdd:{ - Bdd: () => new CutSetGenerator() - } + bdd: { + Bdd: () => new CutSetGenerator(), + }, }; diff --git a/extension/src-language-server/fta/fta-utils.ts b/extension/src-language-server/fta/fta-utils.ts index 039dd54..124366f 100644 --- a/extension/src-language-server/fta/fta-utils.ts +++ b/extension/src-language-server/fta/fta-utils.ts @@ -15,14 +15,20 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { AstNode } from 'langium'; -import { IdCache } from 'langium-sprotty'; -import { Gate, isAND, isComponent, isCondition, isGate, isInhibitGate, isKNGate, isOR, isTopEvent } from '../generated/ast'; -import { FTNodeType } from './fta-model'; - - - - +import { AstNode } from "langium"; +import { IdCache } from "langium-sprotty"; +import { + Gate, + isAND, + isComponent, + isCondition, + isGate, + isInhibitGate, + isKNGate, + isOR, + isTopEvent, +} from "../generated/ast"; +import { FTNodeType } from "./fta-model"; /** * Getter for the references contained in {@code node}. @@ -31,25 +37,29 @@ import { FTNodeType } from './fta-model'; */ export function getTargets(node: AstNode): AstNode[] { const targets: AstNode[] = []; - if(isTopEvent(node)){ + if (isTopEvent(node)) { for (const ref of node.children) { - if (ref?.ref) { targets.push(ref.ref); } + if (ref?.ref) { + targets.push(ref.ref); + } } - }else if(isGate(node)){ - for(const ref of node.type.children){ - if(ref?.ref){targets.push(ref.ref);} + } else if (isGate(node)) { + for (const ref of node.type.children) { + if (ref?.ref) { + targets.push(ref.ref); + } } - if(isInhibitGate(node.type)){ - for(const ref of node.type.condition){ - if(ref?.ref){targets.push(ref.ref);} + if (isInhibitGate(node.type)) { + for (const ref of node.type.condition) { + if (ref?.ref) { + targets.push(ref.ref); + } } } } return targets; - } - /** * Getter for the type of a FTA component. * @param node AstNode which type should be determined. @@ -57,29 +67,28 @@ export function getTargets(node: AstNode): AstNode[] { */ export function getFTNodeType(node: AstNode): FTNodeType { if (isTopEvent(node)) { - return FTNodeType.TOPEVENT; - }else if (isComponent(node)) { + return FTNodeType.TOPEVENT; + } else if (isComponent(node)) { return FTNodeType.COMPONENT; - }else if (isCondition(node)) { + } else if (isCondition(node)) { return FTNodeType.CONDITION; - }else if(isGate(node) && isAND(node.type)){ + } else if (isGate(node) && isAND(node.type)) { return FTNodeType.AND; - }else if (isGate(node) && isOR(node.type)) { + } else if (isGate(node) && isOR(node.type)) { return FTNodeType.OR; - }else if (isGate(node) && isKNGate(node.type)) { + } else if (isGate(node) && isKNGate(node.type)) { return FTNodeType.KN; - }else if (isGate(node) && isInhibitGate(node.type)) { + } else if (isGate(node) && isInhibitGate(node.type)) { return FTNodeType.INHIBIT; } return FTNodeType.UNDEFINED; } - /** Sorts every gate with its type and puts them into a two dimensional array * @param gates Every gate within the FTAModel * @returns A two dimensional array with every gate sorted into the respective category of And, Or, KN, Inhibit-Gate */ -export function getAllGateTypes(gates: Gate[]): Map{ +export function getAllGateTypes(gates: Gate[]): Map { const allGates: Map = new Map(); const andGates: AstNode[] = []; @@ -87,23 +96,22 @@ export function getAllGateTypes(gates: Gate[]): Map{ const kNGates: AstNode[] = []; const inhibGates: AstNode[] = []; - for(const gate of gates){ - if(isAND(gate.type)){ + for (const gate of gates) { + if (isAND(gate.type)) { andGates.push(gate); - }else if(isOR(gate.type)){ + } else if (isOR(gate.type)) { orGates.push(gate); - }else if(isKNGate(gate.type)){ + } else if (isKNGate(gate.type)) { kNGates.push(gate); - }else if(isInhibitGate(gate.type)){ + } else if (isInhibitGate(gate.type)) { inhibGates.push(gate); } } - - allGates.set('AND', andGates); - allGates.set('OR', orGates); - allGates.set('KNGate', kNGates); - allGates.set('InhibitGate', inhibGates); + allGates.set("AND", andGates); + allGates.set("OR", orGates); + allGates.set("KNGate", kNGates); + allGates.set("InhibitGate", inhibGates); return allGates; } @@ -113,7 +121,7 @@ export function getAllGateTypes(gates: Gate[]): Map{ * @param idCache The idCache of the generator context from the current graph. * @returns a string that contains every cut set. */ -export function cutSetToString(cutSets:AstNode[][], idCache:IdCache):string{ +export function cutSetToString(cutSets: AstNode[][], idCache: IdCache): string { const result = "The resulting " + cutSets.length + " cut sets are: \n"; return setToString(cutSets, idCache, result); } @@ -124,37 +132,35 @@ export function cutSetToString(cutSets:AstNode[][], idCache:IdCache):st * @param idCache The idCache of the generator context from the current graph. * @returns a string that contains every minimal cut set. */ -export function minimalCutSetToString(minimalCutSets:AstNode[][], idCache:IdCache):string{ +export function minimalCutSetToString(minimalCutSets: AstNode[][], idCache: IdCache): string { const result = "The resulting " + minimalCutSets.length + " minimal cut sets are: \n"; return setToString(minimalCutSets, idCache, result); } - /** * Takes all (minimal) cut sets and returns a string that resembles it, so it can be displayed in the console. * @param cutSets The (minimal) cut sets of the current Fault Tree. * @returns A string that resembles the cut sets. -*/ -export function setToString(cutSets:AstNode[][], idCache:IdCache, result:string):string{ + */ +export function setToString(cutSets: AstNode[][], idCache: IdCache, result: string): string { result += "["; - for(const set of cutSets){ + for (const set of cutSets) { result += "["; - for(const element of set){ - if(set.indexOf(element) === set.length -1){ + for (const element of set) { + if (set.indexOf(element) === set.length - 1) { result += idCache.getId(element); - }else{ + } else { result = result + idCache.getId(element) + ","; } } result += "]"; - if(cutSets.indexOf(set) === cutSets.length -1){ + if (cutSets.indexOf(set) === cutSets.length - 1) { result += "]\n"; - }else{ + } else { result += ",\n"; } } return result; } - diff --git a/extension/src-language-server/fta/fta-validator.ts b/extension/src-language-server/fta/fta-validator.ts index 52839f7..8ae0a55 100644 --- a/extension/src-language-server/fta/fta-validator.ts +++ b/extension/src-language-server/fta/fta-validator.ts @@ -15,9 +15,9 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { ValidationAcceptor, ValidationChecks, ValidationRegistry } from 'langium'; -import { ModelFTA, PastaAstType } from '../generated/ast'; -import type { FtaServices } from './fta-module'; +import { ValidationAcceptor, ValidationChecks, ValidationRegistry } from "langium"; +import { ModelFTA, PastaAstType } from "../generated/ast"; +import type { FtaServices } from "./fta-module"; /** * Registry for validation checks. @@ -37,44 +37,41 @@ export class FtaValidationRegistry extends ValidationRegistry { * Implementation of custom validations. */ export class FtaValidator { - /** * Executes validation checks for the whole model. * @param model The model to validate. - * @param accept + * @param accept */ - checkModel(model: ModelFTA, accept: ValidationAcceptor): void{ + checkModel(model: ModelFTA, accept: ValidationAcceptor): void { this.checkUniqueIdentifiers(model, accept); } /** * Prevent multiple components, conditions and gates from having the same identifier. * @param model The model to validate. - * @param accept + * @param accept */ - checkUniqueIdentifiers(model: ModelFTA, accept: ValidationAcceptor): void{ + checkUniqueIdentifiers(model: ModelFTA, accept: ValidationAcceptor): void { const componentNames = new Set(); - model.components.forEach(c => { + model.components.forEach((c) => { if (componentNames.has(c.name)) { - accept('error', `Component has non-unique name '${c.name}'.`, {node: c, property: 'name'}); + accept("error", `Component has non-unique name '${c.name}'.`, { node: c, property: "name" }); } componentNames.add(c.name); }); - model.conditions.forEach(c => { - if(componentNames.has(c.name)){ - accept('error', `Condition has non-unique name '${c.name}'.`, {node: c, property: 'name'}); + model.conditions.forEach((c) => { + if (componentNames.has(c.name)) { + accept("error", `Condition has non-unique name '${c.name}'.`, { node: c, property: "name" }); } componentNames.add(c.name); }); const gateNames = new Set(); - model.gates.forEach(g => { + model.gates.forEach((g) => { if (gateNames.has(g.name) || componentNames.has(g.name)) { - accept('error', `Gate has non-unique name '${g.name}'.`, {node: g, property: 'name'}); + accept("error", `Gate has non-unique name '${g.name}'.`, { node: g, property: "name" }); } gateNames.add(g.name); }); - } - } diff --git a/extension/src-language-server/fta/synthesis-options.ts b/extension/src-language-server/fta/synthesis-options.ts index e9bc9f0..9cb3b47 100644 --- a/extension/src-language-server/fta/synthesis-options.ts +++ b/extension/src-language-server/fta/synthesis-options.ts @@ -18,10 +18,8 @@ import { SynthesisOptions } from "../synthesis-options"; export class FtaSynthesisOptions extends SynthesisOptions { - constructor() { super(); // this.options = []; } - -} \ No newline at end of file +} diff --git a/extension/src-webview/fta-model.ts b/extension/src-webview/fta-model.ts index 092d2cd..bc579b8 100644 --- a/extension/src-webview/fta-model.ts +++ b/extension/src-webview/fta-model.ts @@ -15,30 +15,40 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { SEdge, SNode, connectableFeature, fadeFeature, hoverFeedbackFeature, layoutContainerFeature, popupFeature, selectFeature } from "sprotty"; - - +import { + SEdge, + SNode, + connectableFeature, + fadeFeature, + hoverFeedbackFeature, + layoutContainerFeature, + popupFeature, + selectFeature, +} from "sprotty"; // The types of diagram elements -export const FTA_NODE_TYPE = 'node:fta'; -export const TREE_TYPE = 'node:tree'; -export const FTA_EDGE_TYPE = 'edge:fta'; - - +export const FTA_NODE_TYPE = "node:fta"; +export const TREE_TYPE = "node:tree"; +export const FTA_EDGE_TYPE = "edge:fta"; /** * Node representing a FTA component. */ -export class FTANode extends SNode{ - static readonly DEFAULT_FEATURES = [connectableFeature, selectFeature, - layoutContainerFeature, fadeFeature, hoverFeedbackFeature, popupFeature]; +export class FTANode extends SNode { + static readonly DEFAULT_FEATURES = [ + connectableFeature, + selectFeature, + layoutContainerFeature, + fadeFeature, + hoverFeedbackFeature, + popupFeature, + ]; nodeType: FTNodeType = FTNodeType.UNDEFINED; description: string = ""; highlight?: boolean; k?: number; n?: number; - } /** @@ -48,7 +58,6 @@ export class FTAEdge extends SEdge { highlight?: boolean; } - /** * The different types of nodes of FTA. */ @@ -60,5 +69,5 @@ export enum FTNodeType { OR, KN, INHIBIT, - UNDEFINED -} \ No newline at end of file + UNDEFINED, +} diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index f8d3e1f..64e2b43 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -24,7 +24,6 @@ import { FTAEdge, FTANode, FTA_EDGE_TYPE, FTNodeType } from './fta-model'; import { CutSetsRegistry } from './options/cut-set-registry'; import { renderAndGate, renderCircle, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "./views-rendering"; - @injectable() export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { @@ -36,18 +35,18 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { path += ` L ${p.x},${p.y}`; } - if((edge.target as FTANode).highlight === true){ + if ((edge.target as FTANode).highlight === true) { (edge as FTAEdge).highlight = true; - }else{ + } else { (edge as FTAEdge).highlight = false; } // if an FTANode is selected, the components not connected to it should fade out - const hidden = edge.type == FTA_EDGE_TYPE && !(edge as FTAEdge).highlight; - + const hidden = edge.type === FTA_EDGE_TYPE && !(edge as FTAEdge).highlight; + return ; } - + } @@ -55,10 +54,8 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { export class FTANodeView extends RectangularNodeView { @inject(DISymbol.CutSetsRegistry) cutSetsRegistry: CutSetsRegistry; - - render(node: FTANode, context: RenderingContext): VNode { - + render(node: FTANode, context: RenderingContext): VNode { // create the element based on the type of the node let element: VNode; switch (node.nodeType) { @@ -85,39 +82,39 @@ export class FTANodeView extends RectangularNodeView { break; default: element = renderRectangle(node); - break; + break; } //highlight every node that is in the selected cut set or on the path to the top event. let set = this.cutSetsRegistry.getCurrentValue(); let onlyInCutSet = false; - if(set !== undefined){ + if (set !== undefined) { //highlight all when the empty cut set is selected - if(set === '-' ){ + if (set === '-') { node.highlight = true; - }else{ + } else { //unhighlight every node first and then only highlight the correct ones. node.highlight = false; - if(node.nodeType === FTNodeType.COMPONENT || node.nodeType === FTNodeType.CONDITION){ + if (node.nodeType === FTNodeType.COMPONENT || node.nodeType === FTNodeType.CONDITION) { //node is component or condition and in the selected cut set. - if(set.includes(node.id)){ + if (set.includes(node.id)) { node.highlight = true; onlyInCutSet = true; - }else{ + } else { //all other components and conditions are not highlighted. - node.highlight = false; - onlyInCutSet= false; + node.highlight = false; + onlyInCutSet = false; } - }else{ + } else { //check if a gate should be highlighted - if(this.checkIfHighlighted(node, set) === true){ + if (this.checkIfHighlighted(node, set) === true) { node.highlight = true; - }else{ + } else { node.highlight = false; } } - } + } } //if an FTANode is selected, the components not connected to it should fade out @@ -139,15 +136,15 @@ export class FTANodeView extends RectangularNodeView { * @param set The set of all highlighted nodes. * @returns True if the node is connected to a node from the set or false otherwise. */ - checkIfHighlighted(node: FTANode, set: any):boolean{ - for(const edge of node.outgoingEdges){ + checkIfHighlighted(node: FTANode, set: any): boolean { + for (const edge of node.outgoingEdges) { let target = (edge.target as FTANode); - if((target.nodeType === FTNodeType.COMPONENT || target.nodeType === FTNodeType.CONDITION)){ - if(set.includes(target.id)){ + if ((target.nodeType === FTNodeType.COMPONENT || target.nodeType === FTNodeType.CONDITION)) { + if (set.includes(target.id)) { return true; } - }else{ - if(this.checkIfHighlighted(target, set) === true){ + } else { + if (this.checkIfHighlighted(target, set) === true) { return true; } } @@ -157,8 +154,7 @@ export class FTANodeView extends RectangularNodeView { } @injectable() -export class FTAGraphView extends RectangularNodeView{ - +export class FTAGraphView extends RectangularNodeView { render(node: SNode, context: RenderingContext): VNode { diff --git a/extension/src-webview/options/cut-set-panel.tsx b/extension/src-webview/options/cut-set-panel.tsx index 3b3c17f..42d9c53 100644 --- a/extension/src-webview/options/cut-set-panel.tsx +++ b/extension/src-webview/options/cut-set-panel.tsx @@ -16,26 +16,23 @@ */ /** @jsx html */ -import { SidebarPanel } from "../sidebar"; -import { html } from "sprotty"; -import { DISymbol } from "../di.symbols"; import { inject, injectable, postConstruct } from "inversify"; import { VNode } from "snabbdom"; +import { html } from "sprotty"; +import { DISymbol } from "../di.symbols"; import { FeatherIcon } from '../feather-icons-snabbdom/feather-icons-snabbdom'; +import { SidebarPanel } from "../sidebar"; import { CutSetsRegistry } from "./cut-set-registry"; import { OptionsRenderer } from "./options-renderer"; @injectable() -export class CutSetPanel extends SidebarPanel{ - +export class CutSetPanel extends SidebarPanel { @inject(DISymbol.CutSetsRegistry) private cutSetsRegistry: CutSetsRegistry; @inject(DISymbol.OptionsRenderer) private optionsRenderer: OptionsRenderer; - - @postConstruct() - init():void{ + init(): void { this.cutSetsRegistry.onChange(() => this.update()); } get id(): string { @@ -60,6 +57,6 @@ export class CutSetPanel extends SidebarPanel{ } get icon(): VNode { - return ; + return ; } } \ No newline at end of file diff --git a/extension/src-webview/options/cut-set-registry.ts b/extension/src-webview/options/cut-set-registry.ts index f9a98ad..94d7d42 100644 --- a/extension/src-webview/options/cut-set-registry.ts +++ b/extension/src-webview/options/cut-set-registry.ts @@ -16,64 +16,61 @@ */ import { injectable } from "inversify"; -import { ICommand, IActionHandlerInitializer, ActionHandlerRegistry } from "sprotty"; +import { ICommand } from "sprotty"; import { Action, UpdateModelAction } from "sprotty-protocol"; import { Registry } from "../base/registry"; import { SendCutSetAction } from "./actions"; -import { DropDownOption, TransformationOptionType } from './option-models'; +import { DropDownOption, TransformationOptionType } from "./option-models"; - -export class DropDownMenuOption implements DropDownOption{ - static readonly ID: string = 'cut-sets'; - static readonly NAME: string = 'Cut Sets'; +export class DropDownMenuOption implements DropDownOption { + static readonly ID: string = "cut-sets"; + static readonly NAME: string = "Cut Sets"; readonly id: string = DropDownMenuOption.ID; - readonly currentId:string = DropDownMenuOption.ID; + readonly currentId: string = DropDownMenuOption.ID; readonly name: string = DropDownMenuOption.NAME; readonly type: TransformationOptionType = TransformationOptionType.DROPDOWN; - values: { displayName: string; id: string }[] = [{displayName: "---", id: "---"}]; - availableValues: { displayName: string; id: string }[] = [{displayName: "---", id: "---"}]; - readonly initialValue: { displayName: string; id: string } = {displayName: "---", id: "---"}; - currentValue = {displayName: "---", id: "---"}; + values: { displayName: string; id: string }[] = [{ displayName: "---", id: "---" }]; + availableValues: { displayName: string; id: string }[] = [{ displayName: "---", id: "---" }]; + readonly initialValue: { displayName: string; id: string } = { displayName: "---", id: "---" }; + currentValue = { displayName: "---", id: "---" }; } - /** {@link Registry} that stores and updates different render options. */ @injectable() -export class CutSetsRegistry extends Registry{ - +export class CutSetsRegistry extends Registry { private _options: Map = new Map(); - handle(action: Action): void | Action | ICommand{ - if(SendCutSetAction.isThisAction(action)){ + handle(action: Action): void | Action | ICommand { + if (SendCutSetAction.isThisAction(action)) { const dropDownOption = new DropDownMenuOption(); - for(const set of action.cutSets){ - dropDownOption.availableValues.push({displayName: set.value , id: set.value}); + for (const set of action.cutSets) { + dropDownOption.availableValues.push({ displayName: set.value, id: set.value }); } - this._options.set('cut-sets', dropDownOption); + this._options.set("cut-sets", dropDownOption); this.notifyListeners(); } return UpdateModelAction.create([], { animate: false, cause: action }); } - get allOptions():DropDownOption[]{ + get allOptions(): DropDownOption[] { return Array.from(this._options.values()); } - getCurrentValue():any{ + getCurrentValue(): any { //if the cut sets were not requested yet, there is nothing to highlight - if(this._options.get('cut-sets')?.availableValues.length === 1){ + if (this._options.get("cut-sets")?.availableValues.length === 1) { return undefined; } - const selectedCutSet:{ displayName: string; id: string } = this._options.get('cut-sets')?.currentValue; - if(selectedCutSet){ + const selectedCutSet: { displayName: string; id: string } = this._options.get("cut-sets")?.currentValue; + if (selectedCutSet) { //slice the brackets at the start and at the end. - const selected = selectedCutSet.displayName.slice(1,-1); - if(selected === '-'){ - return '-'; - }else{ + const selected = selectedCutSet.displayName.slice(1, -1); + if (selected === "-") { + return "-"; + } else { return selected.split(","); } - } + } } -} \ No newline at end of file +} From 091209c137ca13ebcd56106b2b6cd1516e5a3e7a Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Wed, 20 Sep 2023 16:13:50 +0200 Subject: [PATCH 37/61] revision (WIP) --- .../fta/fta-cutSet-generator.ts | 283 +++++++++--------- extension/src/extension.ts | 11 +- 2 files changed, 152 insertions(+), 142 deletions(-) diff --git a/extension/src-language-server/fta/fta-cutSet-generator.ts b/extension/src-language-server/fta/fta-cutSet-generator.ts index 7cc1ea6..cf718f4 100644 --- a/extension/src-language-server/fta/fta-cutSet-generator.ts +++ b/extension/src-language-server/fta/fta-cutSet-generator.ts @@ -31,33 +31,34 @@ import { export class CutSetGenerator { /** - * Takes the Fault Tree and returns a two-dimensional array of AstNodes where every inner list resembles a minimal cut set. - * @param allNodes All Nodes in the graph. - * @param idCache The idCache of the generator context from the current graph. + * Takes the Fault Tree and returns a two-dimensional array of AstNodes where every inner list resembles a minimal + * cut set. + * @param allNodes All Nodes in the fault tree. + * @param idCache The idCache of the generator context from the corresponding graph. * @returns A list of lists that that contains every minimal cut set of the given Fault Tree. */ determineMinimalCutSet(allNodes: AstNode[], idCache: IdCache): AstNode[][] { - const bdd = this.generateCutSets(allNodes, idCache); + // TODO: add minimal flag (could reduce computation cost) + const allCutSets = this.generateCutSetsForFT(allNodes, idCache); - //Cut sets are minimal if, when any basic event is removed from the set, the remaining events collectively are no longer a cut set. - //Check every innerList - //If inner list contains another array from the bdd array, remove innerList because it cant be a minimal cut set - const minimalCutSet = bdd.filter((innerList) => { - return this.checkIfMinimalCutSet(innerList, bdd); //if this condition is true then the innerList is a minimal cut set + // Cut sets are minimal if removing one element destroys the cut set + // If an inner list contains another array from the bdd array, remove it since it is not minimal + const minimalCutSet = allCutSets.filter((cutSet) => { + return this.checkIfMinimalCutSet(cutSet, allCutSets); }); return minimalCutSet; } /** - * Takes a list and all cut sets and checks if the given list is a minimal cut set. - * @param innerList The list we want to check. - * @param bdd All Cut Sets of the Fault Tree - * @returns True if the given list is a minimal cut set or false if is not. + * Checks whether the given list {@code innerList} is a minimal cut set. + * @param cutSet The list to check. + * @param allCutSets All Cut Sets of the Fault Tree. + * @returns True if the given list is a minimal cut set. */ - checkIfMinimalCutSet(innerList: AstNode[], bdd: AstNode[][]): boolean { - for (const list of bdd) { - if (list.every((e) => innerList.includes(e)) && innerList !== list) { + protected checkIfMinimalCutSet(cutSet: AstNode[], allCutSets: AstNode[][]): boolean { + for (const otherCutSet of allCutSets) { + if (otherCutSet.every((element) => cutSet.includes(element)) && cutSet !== otherCutSet) { return false; } } @@ -65,83 +66,80 @@ export class CutSetGenerator { } /** - * Takes the Fault Tree and returns a two-dimensional array of AstNodes where every inner list resembles a cut set. - * @param allNodes All Nodes in the graph. - * @param idCache The idCache of the generator context from the current graph. - * @returns A list of lists that that contains every cut set of the given Fault Tree. + * Determines all cut sets of a fault tree. + * @param allNodes All nodes of the fault tree. + * @param idCache The idCache of the generator context from the corresponding graph. + * @returns A list of lists that contains every cut set of the given Fault Tree. */ - generateCutSets(allNodes: AstNode[], idCache: IdCache): AstNode[][] { - //Idea: - //Start from the top event. - //Get the only child of top event (will always be only one) as our starting node. - //Calculate all children of the node and evaluate them. - //In the evaluation we check if the child has children too and do the same recursively until the children are components. - //Depending on the type of the node process the results of the children differently. + generateCutSetsForFT(allNodes: AstNode[], idCache: IdCache): AstNode[][] { + /* Idea: + Start from the top event. + Get the only child of top event (will always be only one) as our starting node. + Calculate all children of the node and evaluate them. + In the evaluation we check if the child has children too and do the same recursively until + the children are components. + Depending on the type of the node process the results of the children differently. */ - //When there is no gate, return the component const startNode = this.getChildOfTopEvent(allNodes); if (startNode) { + // When the start not is not a gate, it is the only cut set if (isComponent(startNode)) { return [[startNode]]; } - //Evaluate the child of the top event and recursively the entire Fault Tree. - const cutSets = this.evaluate(startNode, allNodes, idCache); - return cutSets; - } else { - return []; + // determine the cut sets of the Fault Tree + return this.determineCutSetsForGate(startNode, allNodes, idCache); } + return []; } /** - * Takes a single node and returns it evaluation depending on the node type and number of children. This function is called recursively for all children. - * @param node The node we want to evaluate. - * @param allNodes All Nodes in the graph. - * @param idCache The idCache of the generator context from the current graph. - * @returns A list of lists that is the result of evaluating the given node. + * Determines the cut sets for the (sub) fault tree that has {@code gate} as the top node. + * @param gate The top node of the (sub) fault tree for which the cut sets should be determined. + * @param allNodes All nodes of the fault tree. + * @param idCache The idCache of the generator context from the corresponding graph. + * @returns the determined cut sets for the (sub) fault tree as a list of lists. */ - evaluate(node: AstNode, allNodes: AstNode[], idCache: IdCache): AstNode[][] { + protected determineCutSetsForGate(gate: AstNode, allNodes: AstNode[], idCache: IdCache): AstNode[][] { let result: AstNode[][] = []; - // we start with the top-most gate(child of topevent) and get all its children. - const children = this.getAllChildrenOfNode(node); - if (children.length === 0) { + const children = this.getChildrenOfNode(gate); + // TODO: return list containing only the gate, when it is not a gate + if (children.length === 0 || !isGate(gate)) { return result; } - //if the node is an and/inhibit-gate we want to evaluate all children and concatenate all inner lists of one child with another. - if (isGate(node) && (isAND(node.type) || isInhibitGate(node.type))) { + if (isAND(gate.type) || isInhibitGate(gate.type)) { + // concatenate each cut set of a child with every cut set of the other children for (const child of children) { if (isComponent(child) || isCondition(child)) { - result = this.concatAllLists([[child]], result, idCache); + result = this.concatInnerListsWithEachOther([[child]], result, idCache); } else { - result = this.concatAllLists(this.evaluate(child, allNodes, idCache), result, idCache); + result = this.concatInnerListsWithEachOther( + this.determineCutSetsForGate(child, allNodes, idCache), + result, + idCache + ); } } - //if the node is an or-gate we want to evaluate all children and add every single inner list to the result. - } else if (isGate(node) && isOR(node.type)) { + } else if (isOR(gate.type)) { + // add the cut sets of each child to the result for (const child of children) { if (isComponent(child)) { - const orList = [child]; - result.push(orList); + result.push([child]); } else { - //push every inner list of the child gate. - for (const list of this.evaluate(child, allNodes, idCache)) { - result.push(list); - } + result.push(...this.determineCutSetsForGate(child, allNodes, idCache)); } } + } else if (isKNGate(gate.type)) { + // TODO: inspect + const k = gate.type.k; + const n = gate.type.children.length; - //if the node is a kN-gate we want to get every combinations of the children with length k and after that evaluate the gates in the list. - } else if (isGate(node) && isKNGate(node.type)) { - const k = node.type.k as number; - const n = node.type.children.length as number; - - //Example: With Children:[M1,M2,G1] and k=2 -> [[M1,M2],[M1,G1],[M2,G1]] . + // determine every combination of the children with length k or greater + // Example: children = [M1, M2, G1] and k = 2 -> [[M1, M2], [M1, G1], [M2, G1], [M1, M2, G1]] const combinations: AstNode[][] = []; for (let i = k; i <= n; i++) { - for (const comb of this.getAllCombinations(children, i)) { - combinations.push(comb); - } + combinations.push(...this.createAllCombinations(children, i)); } //Now we want to evaluate G1 from the example above (e.g evaluation(G1) = [[C]]). @@ -149,13 +147,16 @@ export class CutSetGenerator { for (const comb of combinations) { if ( comb.some( - (e) => isGate(e) && (isAND(e.type) || isInhibitGate(e.type) || isOR(e.type) || isKNGate(e.type)) + (element) => + isGate(element) && + (isAND(element.type) || + isInhibitGate(element.type) || + isOR(element.type) || + isKNGate(element.type)) ) ) { const evaluatedLists = this.evaluateGateInCombinationList(comb, allNodes, idCache); - for (const list of evaluatedLists) { - result.push(list); - } + result.push(...evaluatedLists); } else { result.push(comb); } @@ -165,6 +166,7 @@ export class CutSetGenerator { return result; } + // TODO: inspect /** * Takes a list of components, conditions and gates and then removes the gates and inserts its evaluation in the list. This can result in multiple lists. * @param innerList The list we want to evaluate. @@ -172,23 +174,28 @@ export class CutSetGenerator { * @param idCache The idCache of the generator context from the current graph. * @returns A list of lists that is the result of inserting the evaluation of the gates in the given list. */ - evaluateGateInCombinationList(innerList: AstNode[], allNodes: AstNode[], idCache: IdCache): AstNode[][] { + protected evaluateGateInCombinationList( + innerList: AstNode[], + allNodes: AstNode[], + idCache: IdCache + ): AstNode[][] { let result: AstNode[][] = []; const restList: AstNode[] = innerList; for (let i = 0; i < restList.length; i++) { const element = restList[i]; - //when the element is a gate. - if ( - isGate(element) && - (isAND(element.type) || isInhibitGate(element.type) || isOR(element.type) || isKNGate(element.type)) - ) { + // when the element is a gate. + if (isGate(element)) { //cut out the gate from the rest list. const index = restList.indexOf(element); restList.splice(index, 1); i -= 1; //and push the evaluation of the gate into the result list. - const tempLists = this.concatAllLists(this.evaluate(element, allNodes, idCache), result, idCache); + const tempLists = this.concatInnerListsWithEachOther( + this.determineCutSetsForGate(element, allNodes, idCache), + result, + idCache + ); result = []; for (const list of tempLists) { result.push(list); @@ -197,19 +204,19 @@ export class CutSetGenerator { } //concatenate every element of the rest list with the result (should only be components/conditions). for (const list of restList) { - result = this.concatAllLists([[list]], result, idCache); + result = this.concatInnerListsWithEachOther([[list]], result, idCache); } return result; } /** - * Gets all combinations of the elements in the given list with length k. - * @param nodes The list of nodes we want the combinations of. - * @param k The number of elements we want in an innerList. - * @returns the combinations of the elements in the given list with length k. + * Create all combinations with length {@code k} of the given {@code nodes}. + * @param nodes The list of nodes for which all combinations should be created. + * @param k The number of elements in a combination. + * @returns all combinations with length {@code k} of the given {@ode ndoes}. */ - getAllCombinations(nodes: AstNode[], k: number): AstNode[][] { + protected createAllCombinations(nodes: AstNode[], k: number): AstNode[][] { const combinations: AstNode[][] = []; if (k > nodes.length || k <= 0) { @@ -219,17 +226,14 @@ export class CutSetGenerator { return [nodes]; } if (k === 1) { - for (let i = 0; i < nodes.length; i++) { - combinations.push([nodes[i]]); - } + nodes.forEach(node => combinations.push([node])); } for (let i = 0; i < nodes.length; i++) { - const currElement: AstNode = nodes[i]; - const restOfList = nodes.slice(i + 1); - for (const subComps of this.getAllCombinations(restOfList, k - 1)) { - subComps.unshift(currElement); - combinations.push(subComps); + const currentNode: AstNode = nodes[i]; + for (const subElements of this.createAllCombinations(nodes.slice(i + 1), k - 1)) { + subElements.unshift(currentNode); + combinations.push(subElements); } } @@ -237,32 +241,29 @@ export class CutSetGenerator { } /** - * Take an AstNode and return all its children(just the next hierarchy level). - * @param parentNode The node we want the children of. - * @returns all children of the given parentNode. + * Determines all the children of {@code node}. + * @param node The node for which the children should be determined. + * @returns all children of the given {@code node}. */ - getAllChildrenOfNode(parentNode: AstNode): AstNode[] { + protected getChildrenOfNode(node: AstNode): AstNode[] { const children: AstNode[] = []; - if (isComponent(parentNode) || isCondition(parentNode)) { + if (isComponent(node) || isCondition(node)) { + // node has no children return children; } - if ( - isGate(parentNode) && - (isAND(parentNode.type) || - isOR(parentNode.type) || - isInhibitGate(parentNode.type) || - isKNGate(parentNode.type)) - ) { - for (const ref of parentNode.type.children) { - if (ref?.ref) { - children.push(ref.ref); + if (isGate(node)) { + // add children of the gate + for (const childRef of node.type.children) { + if (childRef.ref) { + children.push(childRef.ref); } } - if (isInhibitGate(parentNode.type)) { - for (const ref of parentNode.type.condition) { - if (ref?.ref) { - children.push(ref.ref); + // add condition of inhibit gate + if (isInhibitGate(node.type)) { + for (const childRef of node.type.condition) { + if (childRef?.ref) { + children.push(childRef.ref); } } } @@ -271,11 +272,11 @@ export class CutSetGenerator { } /** - * Given all nodes this method returns the first and only child of the topevent. - * @param allNodes All nodes in the graph. - * @returns the child of the topevent. + * Determines the child of the top event. + * @param allNodes All nodes in the fault tree. + * @returns the child of the top event. */ - getChildOfTopEvent(allNodes: AstNode[]): AstNode | undefined { + protected getChildOfTopEvent(allNodes: AstNode[]): AstNode | undefined { const topEventChildren = (allNodes.find((node) => isTopEvent(node)) as TopEvent).children; if (topEventChildren.length !== 0) { return topEventChildren[0].ref; @@ -283,25 +284,33 @@ export class CutSetGenerator { } /** - * Concatenates every inner List of two two-dimensional arrays . - * @param a The first two-dimensional AstNode array. - * @param b The second two-dimensional AstNode array. - * @param idCache The idCache of the generator context from the current graph. - * @returns a two-dimensional array of type AstNode where every innerList of both arrays is concatenated. + * Concatenates every inner List of two two-dimensional arrays with each other. + * E.g. [X1, X2] and [Y1, Y2] result in [[X1, Y1], [X1, Y2], [X2, Y1], [X2, Y2]]. + * @param firstAllCutSets The first two-dimensional array. + * @param secondAllCutSets The second two-dimensional array. + * @param idCache The idCache of the generator context from the corresponding graph. + * @returns a two-dimensional array where every innerList of both arrays is concatenated with each other. */ - concatAllLists(a: AstNode[][], b: AstNode[][], idCache: IdCache): AstNode[][] { + protected concatInnerListsWithEachOther( + firstAllCutSets: AstNode[][], + secondAllCutSets: AstNode[][], + idCache: IdCache + ): AstNode[][] { const result: AstNode[][] = []; - if (a.length === 0) { - return b; + // if one array is empty, return the other + if (firstAllCutSets.length === 0) { + return secondAllCutSets; } - if (b.length === 0) { - return a; + if (secondAllCutSets.length === 0) { + return firstAllCutSets; } - for (const innerA of a) { - for (const innerB of b) { - //Add only unique sets - let newSet = innerA.concat(innerB); - newSet = newSet.filter((e, i) => newSet.indexOf(e) === i); + // concatenate arrays + // TODO: replace array with set + for (const firstCutSet of firstAllCutSets) { + for (const secondCutSet of secondAllCutSets) { + // add only unique sets + let newSet = firstCutSet.concat(secondCutSet); + newSet = newSet.filter((element, index) => newSet.indexOf(element) === index); if (this.indexOfArray(newSet, result, idCache) === -1) { result.push(newSet); } @@ -310,14 +319,15 @@ export class CutSetGenerator { return result; } + // TODO: inspect / needed? /** - * Checks if array a and b are equal by sorting them and comparing their values. - * @param a The first array we want to compare. - * @param b The second array we want to compaare. - * @param idCache The idCache of the generator context from the current graph. - * @returns True if they are equal and false if not. + * Checks whether two arrays are equal by sorting them and comparing their values. + * @param first The first array to compare. + * @param second The second array to compaare. + * @param idCache The idCache of the generator context from the corresponding graph. + * @returns True if the arrays are equal and false otherwise. */ - arrayEquals(a: AstNode[], b: AstNode[], idCache: IdCache): boolean { + protected arrayEquals(first: AstNode[], second: AstNode[], idCache: IdCache): boolean { const sort = (x: AstNode, y: AstNode): number => { const idX = idCache.getId(x); const idY = idCache.getId(y); @@ -326,11 +336,12 @@ export class CutSetGenerator { } return 0; }; - const sortA = a.sort(sort); - const sortB = b.sort(sort); - return a.length === b.length && sortA.every((e, i) => e === sortB[i]); + const sortA = first.sort(sort); + const sortB = second.sort(sort); + return first.length === second.length && sortA.every((e, i) => e === sortB[i]); } + // TODO: inspect / needed? /** * Gets the index of a list if it's contained in a two-dimensional list of AstNodes or -1 otherwise. * @param a The list we want the index of. @@ -338,7 +349,7 @@ export class CutSetGenerator { * @param idCache The idCache of the generator context from the current graph. * @returns the index of the list. */ - indexOfArray(a: AstNode[], b: AstNode[][], idCache: IdCache): number { + protected indexOfArray(a: AstNode[], b: AstNode[][], idCache: IdCache): number { let i = 0; for (const list of b) { if (this.arrayEquals(a, list, idCache)) { diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 693fa31..0d07af4 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -198,13 +198,12 @@ function dispatchCutSetsToWebview(manager: StpaLspVscodeExtension, cutSets:strin cutSets = cutSets.slice(1,-2); const cutSetArray = cutSets.split(",\n"); - const cutSetDropDownList: { value: any; }[] = []; - for(const set of cutSetArray){ - cutSetDropDownList.push({value: set}); - } - manager.endpoints.find(endpoint => endpoint.diagramIdentifier?.diagramType === 'fta')?.sendAction({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); - // manager.endpoints.find(endpoint => endpoint.diagramIdentifier?.uri.endsWith('.fta'))?.sendAction({ kind: SendCutSetAction.KIND, cutSets: cutSetDropDownList } as SendCutSetAction); + const cutSetsList: { value: any; }[] = []; + for(const set of cutSetArray){ + cutSetsList.push({value: set}); } + manager.endpoints.find(endpoint => endpoint.diagramIdentifier?.diagramType === 'fta')?.sendAction({ kind: SendCutSetAction.KIND, cutSets: cutSetsList } as SendCutSetAction); +} function createLanguageClient(context: vscode.ExtensionContext): LanguageClient { const serverModule = context.asAbsolutePath(path.join('pack', 'language-server')); From c0d7ec82770acf3b3ede53c04eea706d387be682 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Thu, 21 Sep 2023 09:17:08 +0200 Subject: [PATCH 38/61] fixed build --- extension/src-language-server/fta/fta-message-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index a31ff6d..c7a62d9 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -40,7 +40,7 @@ function addCutSetsHandler(connection: Connection, ftaServices: FtaServices): vo const nodes = diagramGenerator.getNodes(); const idCache = diagramGenerator.getCache(); - const cutSets = ftaServices.bdd.Bdd.generateCutSets(nodes, idCache); + const cutSets = ftaServices.bdd.Bdd.generateCutSetsForFT(nodes, idCache); const cutSetsToString = cutSetToString(cutSets, idCache); return cutSetsToString; From b64daa281dbf6a9b6e24d77211d8ed91d3c1140e Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Thu, 21 Sep 2023 13:35:20 +0200 Subject: [PATCH 39/61] using sets for cut sets instead of array --- .../fta/fta-cutSet-generator.ts | 204 +++++++----------- .../src-language-server/fta/fta-utils.ts | 32 +-- 2 files changed, 90 insertions(+), 146 deletions(-) diff --git a/extension/src-language-server/fta/fta-cutSet-generator.ts b/extension/src-language-server/fta/fta-cutSet-generator.ts index cf718f4..0c53583 100644 --- a/extension/src-language-server/fta/fta-cutSet-generator.ts +++ b/extension/src-language-server/fta/fta-cutSet-generator.ts @@ -37,15 +37,13 @@ export class CutSetGenerator { * @param idCache The idCache of the generator context from the corresponding graph. * @returns A list of lists that that contains every minimal cut set of the given Fault Tree. */ - determineMinimalCutSet(allNodes: AstNode[], idCache: IdCache): AstNode[][] { + determineMinimalCutSet(allNodes: AstNode[], idCache: IdCache): Set[] { // TODO: add minimal flag (could reduce computation cost) const allCutSets = this.generateCutSetsForFT(allNodes, idCache); // Cut sets are minimal if removing one element destroys the cut set - // If an inner list contains another array from the bdd array, remove it since it is not minimal - const minimalCutSet = allCutSets.filter((cutSet) => { - return this.checkIfMinimalCutSet(cutSet, allCutSets); - }); + // If cut set contains another cut set from the array, remove it since it is not minimal + const minimalCutSet = allCutSets.filter((cutSet) => this.checkIfMinimalCutSet(cutSet, allCutSets)); return minimalCutSet; } @@ -56,9 +54,15 @@ export class CutSetGenerator { * @param allCutSets All Cut Sets of the Fault Tree. * @returns True if the given list is a minimal cut set. */ - protected checkIfMinimalCutSet(cutSet: AstNode[], allCutSets: AstNode[][]): boolean { + protected checkIfMinimalCutSet(cutSet: Set, allCutSets: Set[]): boolean { for (const otherCutSet of allCutSets) { - if (otherCutSet.every((element) => cutSet.includes(element)) && cutSet !== otherCutSet) { + let contained = true; + otherCutSet.forEach((element) => { + if (!cutSet.has(element)) { + contained = false; + } + }); + if (contained && cutSet !== otherCutSet) { return false; } } @@ -71,7 +75,7 @@ export class CutSetGenerator { * @param idCache The idCache of the generator context from the corresponding graph. * @returns A list of lists that contains every cut set of the given Fault Tree. */ - generateCutSetsForFT(allNodes: AstNode[], idCache: IdCache): AstNode[][] { + generateCutSetsForFT(allNodes: AstNode[], idCache: IdCache): Set[] { /* Idea: Start from the top event. Get the only child of top event (will always be only one) as our starting node. @@ -82,10 +86,6 @@ export class CutSetGenerator { const startNode = this.getChildOfTopEvent(allNodes); if (startNode) { - // When the start not is not a gate, it is the only cut set - if (isComponent(startNode)) { - return [[startNode]]; - } // determine the cut sets of the Fault Tree return this.determineCutSetsForGate(startNode, allNodes, idCache); } @@ -93,47 +93,46 @@ export class CutSetGenerator { } /** - * Determines the cut sets for the (sub) fault tree that has {@code gate} as the top node. - * @param gate The top node of the (sub) fault tree for which the cut sets should be determined. + * Determines the cut sets for the (sub) fault tree that has {@code startNode} as the top node. + * @param startNode The top node of the (sub) fault tree for which the cut sets should be determined. * @param allNodes All nodes of the fault tree. * @param idCache The idCache of the generator context from the corresponding graph. * @returns the determined cut sets for the (sub) fault tree as a list of lists. */ - protected determineCutSetsForGate(gate: AstNode, allNodes: AstNode[], idCache: IdCache): AstNode[][] { - let result: AstNode[][] = []; + protected determineCutSetsForGate( + startNode: AstNode, + allNodes: AstNode[], + idCache: IdCache + ): Set[] { + let result: Set[] = []; - const children = this.getChildrenOfNode(gate); - // TODO: return list containing only the gate, when it is not a gate - if (children.length === 0 || !isGate(gate)) { + // components do not have children, so return the component + if (isComponent(startNode) || isCondition(startNode)) { + return [new Set([startNode])]; + } + + const children = this.getChildrenOfNode(startNode); + if (children.length === 0 || !isGate(startNode)) { return result; } - if (isAND(gate.type) || isInhibitGate(gate.type)) { + if (isAND(startNode.type) || isInhibitGate(startNode.type)) { // concatenate each cut set of a child with every cut set of the other children for (const child of children) { - if (isComponent(child) || isCondition(child)) { - result = this.concatInnerListsWithEachOther([[child]], result, idCache); - } else { - result = this.concatInnerListsWithEachOther( - this.determineCutSetsForGate(child, allNodes, idCache), - result, - idCache - ); - } + result = this.concatInnerListsWithEachOther( + this.determineCutSetsForGate(child, allNodes, idCache), + result + ); } - } else if (isOR(gate.type)) { + } else if (isOR(startNode.type)) { // add the cut sets of each child to the result for (const child of children) { - if (isComponent(child)) { - result.push([child]); - } else { - result.push(...this.determineCutSetsForGate(child, allNodes, idCache)); - } + result.push(...this.determineCutSetsForGate(child, allNodes, idCache)); } - } else if (isKNGate(gate.type)) { + } else if (isKNGate(startNode.type)) { // TODO: inspect - const k = gate.type.k; - const n = gate.type.children.length; + const k = startNode.type.k; + const n = startNode.type.children.length; // determine every combination of the children with length k or greater // Example: children = [M1, M2, G1] and k = 2 -> [[M1, M2], [M1, G1], [M2, G1], [M1, M2, G1]] @@ -141,24 +140,18 @@ export class CutSetGenerator { for (let i = k; i <= n; i++) { combinations.push(...this.createAllCombinations(children, i)); } + const setCombinations: Set[] = []; + combinations.forEach(combination => setCombinations.push(new Set(combination))); //Now we want to evaluate G1 from the example above (e.g evaluation(G1) = [[C]]). //Our result list should look like this -> [[M1,M2], [M1,C], [M2,C]]. - for (const comb of combinations) { - if ( - comb.some( - (element) => - isGate(element) && - (isAND(element.type) || - isInhibitGate(element.type) || - isOR(element.type) || - isKNGate(element.type)) - ) - ) { - const evaluatedLists = this.evaluateGateInCombinationList(comb, allNodes, idCache); + for (let i=0; i < combinations.length; i++) { + const comb = combinations[i]; + if (comb.some((element) => isGate(element))) { + const evaluatedLists = this.evaluateGateInCombinationList(setCombinations[i], allNodes, idCache); result.push(...evaluatedLists); } else { - result.push(comb); + result.push(setCombinations[i]); } } } @@ -175,36 +168,28 @@ export class CutSetGenerator { * @returns A list of lists that is the result of inserting the evaluation of the gates in the given list. */ protected evaluateGateInCombinationList( - innerList: AstNode[], + innerList: Set, allNodes: AstNode[], idCache: IdCache - ): AstNode[][] { - let result: AstNode[][] = []; - const restList: AstNode[] = innerList; + ): Set[] { + let result: Set[] = []; + const restList= innerList; - for (let i = 0; i < restList.length; i++) { - const element = restList[i]; - // when the element is a gate. + for (const element of restList) { if (isGate(element)) { - //cut out the gate from the rest list. - const index = restList.indexOf(element); - restList.splice(index, 1); - i -= 1; + // cut out the gate from the rest list. + restList.delete(element); //and push the evaluation of the gate into the result list. const tempLists = this.concatInnerListsWithEachOther( this.determineCutSetsForGate(element, allNodes, idCache), - result, - idCache + result ); - result = []; - for (const list of tempLists) { - result.push(list); - } + result = [...tempLists]; } } //concatenate every element of the rest list with the result (should only be components/conditions). for (const list of restList) { - result = this.concatInnerListsWithEachOther([[list]], result, idCache); + result = this.concatInnerListsWithEachOther([new Set([list])], result); } return result; @@ -226,7 +211,7 @@ export class CutSetGenerator { return [nodes]; } if (k === 1) { - nodes.forEach(node => combinations.push([node])); + nodes.forEach((node) => combinations.push([node])); } for (let i = 0; i < nodes.length; i++) { @@ -284,82 +269,39 @@ export class CutSetGenerator { } /** - * Concatenates every inner List of two two-dimensional arrays with each other. + * Concatenates the cut sets of two sets with each other. * E.g. [X1, X2] and [Y1, Y2] result in [[X1, Y1], [X1, Y2], [X2, Y1], [X2, Y2]]. - * @param firstAllCutSets The first two-dimensional array. - * @param secondAllCutSets The second two-dimensional array. - * @param idCache The idCache of the generator context from the corresponding graph. - * @returns a two-dimensional array where every innerList of both arrays is concatenated with each other. + * @param firstAllCutSets The first set of cut sets. + * @param secondAllCutSets The second set of cut sets. + * @returns a set where every cut set of both sets is concatenated with each other. */ protected concatInnerListsWithEachOther( - firstAllCutSets: AstNode[][], - secondAllCutSets: AstNode[][], - idCache: IdCache - ): AstNode[][] { - const result: AstNode[][] = []; - // if one array is empty, return the other + firstAllCutSets: Set[], + secondAllCutSets: Set[] + ): Set[] { + const result: Set[] = []; + // if one cut set set is empty, return the other if (firstAllCutSets.length === 0) { return secondAllCutSets; } if (secondAllCutSets.length === 0) { return firstAllCutSets; } - // concatenate arrays - // TODO: replace array with set + // concatenate sets for (const firstCutSet of firstAllCutSets) { for (const secondCutSet of secondAllCutSets) { - // add only unique sets - let newSet = firstCutSet.concat(secondCutSet); - newSet = newSet.filter((element, index) => newSet.indexOf(element) === index); - if (this.indexOfArray(newSet, result, idCache) === -1) { + // add the new set if it snot already included in the result + const newSet = new Set([...firstCutSet, ...secondCutSet]); + if (result.every((cutSet) => !eqSet(cutSet, newSet))) { result.push(newSet); } } } return result; } - - // TODO: inspect / needed? - /** - * Checks whether two arrays are equal by sorting them and comparing their values. - * @param first The first array to compare. - * @param second The second array to compaare. - * @param idCache The idCache of the generator context from the corresponding graph. - * @returns True if the arrays are equal and false otherwise. - */ - protected arrayEquals(first: AstNode[], second: AstNode[], idCache: IdCache): boolean { - const sort = (x: AstNode, y: AstNode): number => { - const idX = idCache.getId(x); - const idY = idCache.getId(y); - if (idX && idY) { - return idX > idY ? -1 : 1; - } - return 0; - }; - const sortA = first.sort(sort); - const sortB = second.sort(sort); - return first.length === second.length && sortA.every((e, i) => e === sortB[i]); - } - - // TODO: inspect / needed? - /** - * Gets the index of a list if it's contained in a two-dimensional list of AstNodes or -1 otherwise. - * @param a The list we want the index of. - * @param b The two-dimensional list of AstNodes we want to search in. - * @param idCache The idCache of the generator context from the current graph. - * @returns the index of the list. - */ - protected indexOfArray(a: AstNode[], b: AstNode[][], idCache: IdCache): number { - let i = 0; - for (const list of b) { - if (this.arrayEquals(a, list, idCache)) { - break; - } - i++; - } - if (i >= b.length) { - return -1; - } - return i; - } } + +/** Checks whether two sets of AstNodes are equal */ +const eqSet = (xs: Set, ys: Set): boolean => + xs.size === ys.size && + [...xs].every((x) => ys.has(x)); \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-utils.ts b/extension/src-language-server/fta/fta-utils.ts index 124366f..0c601a9 100644 --- a/extension/src-language-server/fta/fta-utils.ts +++ b/extension/src-language-server/fta/fta-utils.ts @@ -121,7 +121,7 @@ export function getAllGateTypes(gates: Gate[]): Map { * @param idCache The idCache of the generator context from the current graph. * @returns a string that contains every cut set. */ -export function cutSetToString(cutSets: AstNode[][], idCache: IdCache): string { +export function cutSetToString(cutSets: Set[], idCache: IdCache): string { const result = "The resulting " + cutSets.length + " cut sets are: \n"; return setToString(cutSets, idCache, result); } @@ -132,7 +132,7 @@ export function cutSetToString(cutSets: AstNode[][], idCache: IdCache): * @param idCache The idCache of the generator context from the current graph. * @returns a string that contains every minimal cut set. */ -export function minimalCutSetToString(minimalCutSets: AstNode[][], idCache: IdCache): string { +export function minimalCutSetToString(minimalCutSets: Set[], idCache: IdCache): string { const result = "The resulting " + minimalCutSets.length + " minimal cut sets are: \n"; return setToString(minimalCutSets, idCache, result); } @@ -142,24 +142,26 @@ export function minimalCutSetToString(minimalCutSets: AstNode[][], idCache: IdCa * @param cutSets The (minimal) cut sets of the current Fault Tree. * @returns A string that resembles the cut sets. */ -export function setToString(cutSets: AstNode[][], idCache: IdCache, result: string): string { +export function setToString(cutSets: Set[], idCache: IdCache, result: string): string { result += "["; for (const set of cutSets) { result += "["; - for (const element of set) { - if (set.indexOf(element) === set.length - 1) { - result += idCache.getId(element); - } else { - result = result + idCache.getId(element) + ","; - } - } + set.forEach(element => result+=idCache.getId(element)+", "); + // for (const element of set) { + // if (set.indexOf(element) === set.length - 1) { + // result += idCache.getId(element); + // } else { + // result = result + idCache.getId(element) + ","; + // } + // } result += "]"; - if (cutSets.indexOf(set) === cutSets.length - 1) { - result += "]\n"; - } else { - result += ",\n"; - } + result += "\n"; + // if (cutSets.indexOf(set) === cutSets.length - 1) { + // result += "]\n"; + // } else { + // result += ",\n"; + // } } return result; From 5ef163e74dd2e715a72d4f0eeca5ad7fce61d160 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Thu, 21 Sep 2023 14:14:55 +0200 Subject: [PATCH 40/61] revision (WIP) --- .../fta/fta-cutSet-generator.ts | 64 ++++--------------- 1 file changed, 12 insertions(+), 52 deletions(-) diff --git a/extension/src-language-server/fta/fta-cutSet-generator.ts b/extension/src-language-server/fta/fta-cutSet-generator.ts index 0c53583..0f81528 100644 --- a/extension/src-language-server/fta/fta-cutSet-generator.ts +++ b/extension/src-language-server/fta/fta-cutSet-generator.ts @@ -130,71 +130,33 @@ export class CutSetGenerator { result.push(...this.determineCutSetsForGate(child, allNodes, idCache)); } } else if (isKNGate(startNode.type)) { - // TODO: inspect const k = startNode.type.k; const n = startNode.type.children.length; - // determine every combination of the children with length k or greater + // determine every combination (with length k or greater) of the children // Example: children = [M1, M2, G1] and k = 2 -> [[M1, M2], [M1, G1], [M2, G1], [M1, M2, G1]] const combinations: AstNode[][] = []; for (let i = k; i <= n; i++) { combinations.push(...this.createAllCombinations(children, i)); } - const setCombinations: Set[] = []; - combinations.forEach(combination => setCombinations.push(new Set(combination))); - //Now we want to evaluate G1 from the example above (e.g evaluation(G1) = [[C]]). - //Our result list should look like this -> [[M1,M2], [M1,C], [M2,C]]. - for (let i=0; i < combinations.length; i++) { - const comb = combinations[i]; - if (comb.some((element) => isGate(element))) { - const evaluatedLists = this.evaluateGateInCombinationList(setCombinations[i], allNodes, idCache); - result.push(...evaluatedLists); - } else { - result.push(setCombinations[i]); + // determine the cut sets for each combination + // (treat each combination the same way as an AND gate with the combination as its children) + for (const combination of combinations) { + let intermediateResult: Set[] = []; + for (const element of combination) { + intermediateResult = this.concatInnerListsWithEachOther( + this.determineCutSetsForGate(element, allNodes, idCache), + intermediateResult + ); } + result.push(...intermediateResult); } } return result; } - // TODO: inspect - /** - * Takes a list of components, conditions and gates and then removes the gates and inserts its evaluation in the list. This can result in multiple lists. - * @param innerList The list we want to evaluate. - * @param allNodes All Nodes in the graph. - * @param idCache The idCache of the generator context from the current graph. - * @returns A list of lists that is the result of inserting the evaluation of the gates in the given list. - */ - protected evaluateGateInCombinationList( - innerList: Set, - allNodes: AstNode[], - idCache: IdCache - ): Set[] { - let result: Set[] = []; - const restList= innerList; - - for (const element of restList) { - if (isGate(element)) { - // cut out the gate from the rest list. - restList.delete(element); - //and push the evaluation of the gate into the result list. - const tempLists = this.concatInnerListsWithEachOther( - this.determineCutSetsForGate(element, allNodes, idCache), - result - ); - result = [...tempLists]; - } - } - //concatenate every element of the rest list with the result (should only be components/conditions). - for (const list of restList) { - result = this.concatInnerListsWithEachOther([new Set([list])], result); - } - - return result; - } - /** * Create all combinations with length {@code k} of the given {@code nodes}. * @param nodes The list of nodes for which all combinations should be created. @@ -302,6 +264,4 @@ export class CutSetGenerator { } /** Checks whether two sets of AstNodes are equal */ -const eqSet = (xs: Set, ys: Set): boolean => - xs.size === ys.size && - [...xs].every((x) => ys.has(x)); \ No newline at end of file +const eqSet = (xs: Set, ys: Set): boolean => xs.size === ys.size && [...xs].every((x) => ys.has(x)); From 284351f8457c50098d03f3bc260f5c2d4b9e5655 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Thu, 21 Sep 2023 15:12:04 +0200 Subject: [PATCH 41/61] revision (cut set output text, generator class replaced by functions) --- .../fta/analysis/fta-cutSet-calculator.ts | 255 +++++++++++++++++ .../{ => diagram}/fta-diagram-generator.ts | 16 +- .../fta/{ => diagram}/fta-interfaces.ts | 22 +- .../fta/{ => diagram}/fta-layout-config.ts | 0 .../fta/{ => diagram}/fta-model.ts | 5 +- .../fta/{fta-utils.ts => diagram/utils.ts} | 116 ++------ .../fta/fta-cutSet-generator.ts | 267 ------------------ .../fta/fta-message-handler.ts | 17 +- .../src-language-server/fta/fta-module.ts | 13 +- extension/src-language-server/fta/utils.ts | 36 +++ extension/src-webview/di.config.ts | 3 +- extension/src-webview/fta-model.ts | 9 +- 12 files changed, 352 insertions(+), 407 deletions(-) create mode 100644 extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts rename extension/src-language-server/fta/{ => diagram}/fta-diagram-generator.ts (94%) rename extension/src-language-server/fta/{ => diagram}/fta-interfaces.ts (71%) rename extension/src-language-server/fta/{ => diagram}/fta-layout-config.ts (100%) rename extension/src-language-server/fta/{ => diagram}/fta-model.ts (87%) rename extension/src-language-server/fta/{fta-utils.ts => diagram/utils.ts} (58%) delete mode 100644 extension/src-language-server/fta/fta-cutSet-generator.ts create mode 100644 extension/src-language-server/fta/utils.ts diff --git a/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts b/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts new file mode 100644 index 0000000..fa8ec09 --- /dev/null +++ b/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts @@ -0,0 +1,255 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { AstNode } from "langium"; +import { + TopEvent, + isAND, + isComponent, + isCondition, + isGate, + isInhibitGate, + isKNGate, + isOR, + isTopEvent, +} from "../../generated/ast"; +import { namedFtaElement } from "../utils"; + +/** + * Determines the minimal cut sets for the fault tree constructured by {@code allNodes}. + * @param allNodes All nodes in the fault tree. + * @returns the minimal cut sets for a fault tree. + */ +export function determineMinimalCutSet(allNodes: AstNode[]): Set[] { + // TODO: add minimal flag (could reduce computation cost) + const allCutSets = generateCutSetsForFT(allNodes); + + // Cut sets are minimal if removing one element destroys the cut set + // If cut set contains another cut set from the array, remove it since it is not minimal + const minimalCutSet = allCutSets.filter((cutSet) => checkIfMinimalCutSet(cutSet, allCutSets)); + + return minimalCutSet; +} + +/** + * Checks whether the given {@code cutSet} is a minimal cut set. + * @param cutSet The list to check. + * @param allCutSets All Cut Sets of the Fault Tree. + * @returns True if the given set is a minimal cut set. + */ +function checkIfMinimalCutSet(cutSet: Set, allCutSets: Set[]): boolean { + for (const otherCutSet of allCutSets) { + let contained = true; + otherCutSet.forEach((element) => { + if (!cutSet.has(element)) { + contained = false; + } + }); + if (contained && cutSet !== otherCutSet) { + return false; + } + } + return true; +} + +/** + * Determines all cut sets of a fault tree. + * @param allNodes All nodes of the fault tree. + * @returns the cut sets of the fault tree. + */ +export function generateCutSetsForFT(allNodes: AstNode[]): Set[] { + /* Idea: + Start from the top event. + Get the only child of top event (will always be only one) as our starting node. + Calculate all children of the node and evaluate them. + In the evaluation we check if the child has children too and do the same recursively until + the children are components. + Depending on the type of the node process the results of the children differently. */ + + const startNode = getChildOfTopEvent(allNodes); + if (startNode) { + // determine the cut sets of the Fault Tree + return determineCutSetsForGate(startNode, allNodes); + } + return []; +} + +/** + * Determines the cut sets for the (sub) fault tree that has {@code startNode} as the top node. + * @param startNode The top node of the (sub) fault tree for which the cut sets should be determined. + * @param allNodes All nodes of the fault tree. + * @param idCache The idCache of the generator context from the corresponding graph. + * @returns the determined cut sets for the (sub) fault tree as a list of lists. + */ +function determineCutSetsForGate(startNode: AstNode, allNodes: AstNode[]): Set[] { + let result: Set[] = []; + + // components do not have children, so return the component + if (isComponent(startNode) || isCondition(startNode)) { + return [new Set([startNode])]; + } + + const children = getChildrenOfNode(startNode); + if (children.length === 0 || !isGate(startNode)) { + return result; + } + + if (isAND(startNode.type) || isInhibitGate(startNode.type)) { + // concatenate each cut set of a child with every cut set of the other children + for (const child of children) { + result = concatInnerListsWithEachOther(determineCutSetsForGate(child, allNodes), result); + } + } else if (isOR(startNode.type)) { + // add the cut sets of each child to the result + for (const child of children) { + result.push(...determineCutSetsForGate(child, allNodes)); + } + } else if (isKNGate(startNode.type)) { + const k = startNode.type.k; + const n = startNode.type.children.length; + + // determine every combination (with length k or greater) of the children + // Example: children = [M1, M2, G1] and k = 2 -> [[M1, M2], [M1, G1], [M2, G1], [M1, M2, G1]] + const combinations: AstNode[][] = []; + for (let i = k; i <= n; i++) { + combinations.push(...createAllCombinations(children, i)); + } + + // determine the cut sets for each combination + // (treat each combination the same way as an AND gate with the combination as its children) + for (const combination of combinations) { + let combinationResult: Set[] = []; + for (const element of combination) { + combinationResult = concatInnerListsWithEachOther( + determineCutSetsForGate(element, allNodes), + combinationResult + ); + } + result.push(...combinationResult); + } + } + + return result; +} + +/** + * Creates all combinations with length {@code k} of the given {@code nodes}. + * @param nodes The list of nodes for which all combinations should be created. + * @param k The number of elements in a combination. + * @returns all combinations with length {@code k} of the given {@ode ndoes}. + */ +function createAllCombinations(nodes: AstNode[], k: number): AstNode[][] { + const combinations: AstNode[][] = []; + + if (k > nodes.length || k <= 0) { + return []; + } + if (k === nodes.length) { + return [nodes]; + } + if (k === 1) { + nodes.forEach((node) => combinations.push([node])); + } + + for (let i = 0; i < nodes.length; i++) { + const currentNode: AstNode = nodes[i]; + for (const subElements of createAllCombinations(nodes.slice(i + 1), k - 1)) { + subElements.unshift(currentNode); + combinations.push(subElements); + } + } + + return combinations; +} + +/** + * Determines all the children of {@code node}. + * @param node The node for which the children should be determined. + * @returns all children of the given {@code node}. + */ +function getChildrenOfNode(node: AstNode): namedFtaElement[] { + const children: namedFtaElement[] = []; + if (isComponent(node) || isCondition(node)) { + // node has no children + return children; + } + + if (isGate(node)) { + // add children of the gate + for (const childRef of node.type.children) { + if (childRef.ref) { + children.push(childRef.ref); + } + } + // add condition of inhibit gate + if (isInhibitGate(node.type)) { + for (const childRef of node.type.condition) { + if (childRef?.ref) { + children.push(childRef.ref); + } + } + } + } + return children; +} + +/** + * Determines the child of the top event. + * @param allNodes All nodes in the fault tree. + * @returns the child of the top event. + */ +function getChildOfTopEvent(allNodes: AstNode[]): namedFtaElement | undefined { + const topEventChildren = (allNodes.find((node) => isTopEvent(node)) as TopEvent).children; + if (topEventChildren.length !== 0) { + return topEventChildren[0].ref; + } +} + +/** + * Concatenates the cut sets of two sets with each other. + * E.g. [X1, X2] and [Y1, Y2] result in [[X1, Y1], [X1, Y2], [X2, Y1], [X2, Y2]]. + * @param firstAllCutSets The first set of cut sets. + * @param secondAllCutSets The second set of cut sets. + * @returns a set where every cut set of both sets is concatenated with each other. + */ +function concatInnerListsWithEachOther( + firstAllCutSets: Set[], + secondAllCutSets: Set[] +): Set[] { + const result: Set[] = []; + // if one cut set set is empty, return the other + if (firstAllCutSets.length === 0) { + return secondAllCutSets; + } + if (secondAllCutSets.length === 0) { + return firstAllCutSets; + } + // concatenate sets + for (const firstCutSet of firstAllCutSets) { + for (const secondCutSet of secondAllCutSets) { + // add the new set if it snot already included in the result + const newSet = new Set([...firstCutSet, ...secondCutSet]); + if (result.every((cutSet) => !eqSet(cutSet, newSet))) { + result.push(newSet); + } + } + } + return result; +} + +/** Checks whether two sets of AstNodes are equal */ +const eqSet = (xs: Set, ys: Set): boolean => xs.size === ys.size && [...xs].every((x) => ys.has(x)); diff --git a/extension/src-language-server/fta/fta-diagram-generator.ts b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts similarity index 94% rename from extension/src-language-server/fta/fta-diagram-generator.ts rename to extension/src-language-server/fta/diagram/fta-diagram-generator.ts index cab8edc..a1f8c9e 100644 --- a/extension/src-language-server/fta/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts @@ -28,11 +28,11 @@ import { isCondition, isGate, isKNGate, -} from "../generated/ast"; +} from "../../generated/ast"; import { FTAEdge, FTANode } from "./fta-interfaces"; -import { FTA_EDGE_TYPE, FTA_NODE_TYPE, TREE_TYPE } from "./fta-model"; -import { FtaServices } from "./fta-module"; -import { getAllGateTypes, getFTNodeType, getTargets } from "./fta-utils"; +import { FTA_EDGE_TYPE, FTA_NODE_TYPE } from "./fta-model"; +import { FtaServices } from "../fta-module"; +import { getAllGateTypes, getFTNodeType, getTargets } from "./utils"; export class FtaDiagramGenerator extends LangiumDiagramGenerator { allNodes: AstNode[]; @@ -88,13 +88,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { return { type: "graph", id: "root", - children: [ - { - type: TREE_TYPE, - id: "faultTree", - children: ftaChildren, - }, - ], + children: ftaChildren }; } diff --git a/extension/src-language-server/fta/fta-interfaces.ts b/extension/src-language-server/fta/diagram/fta-interfaces.ts similarity index 71% rename from extension/src-language-server/fta/fta-interfaces.ts rename to extension/src-language-server/fta/diagram/fta-interfaces.ts index 621fa68..eaaa9d6 100644 --- a/extension/src-language-server/fta/fta-interfaces.ts +++ b/extension/src-language-server/fta/diagram/fta-interfaces.ts @@ -18,22 +18,20 @@ import { SEdge, SNode } from "sprotty-protocol"; import { FTNodeType } from "./fta-model"; - /** - * Node representing a FTA component. + * Node of a fault tree. */ -export interface FTANode extends SNode{ - nodeType: FTNodeType, - description: string - highlight?: boolean - k?: number - n?: number - +export interface FTANode extends SNode { + nodeType: FTNodeType; + description: string; + highlight?: boolean; + k?: number; + n?: number; } /** - * Edge representing an edge in the fault Tree. + * Edge of a fault tree. */ export interface FTAEdge extends SEdge { - highlight?: boolean -} \ No newline at end of file + highlight?: boolean; +} diff --git a/extension/src-language-server/fta/fta-layout-config.ts b/extension/src-language-server/fta/diagram/fta-layout-config.ts similarity index 100% rename from extension/src-language-server/fta/fta-layout-config.ts rename to extension/src-language-server/fta/diagram/fta-layout-config.ts diff --git a/extension/src-language-server/fta/fta-model.ts b/extension/src-language-server/fta/diagram/fta-model.ts similarity index 87% rename from extension/src-language-server/fta/fta-model.ts rename to extension/src-language-server/fta/diagram/fta-model.ts index 66ec84a..bff1a2a 100644 --- a/extension/src-language-server/fta/fta-model.ts +++ b/extension/src-language-server/fta/diagram/fta-model.ts @@ -15,13 +15,12 @@ * SPDX-License-Identifier: EPL-2.0 */ -//diagram elements +/* fault tree element types */ export const FTA_NODE_TYPE = "node:fta"; export const FTA_EDGE_TYPE = "edge:fta"; -export const TREE_TYPE = "node:tree"; /** - * The different types of nodes of FTA. + * Types of fault tree nodes. */ export enum FTNodeType { TOPEVENT, diff --git a/extension/src-language-server/fta/fta-utils.ts b/extension/src-language-server/fta/diagram/utils.ts similarity index 58% rename from extension/src-language-server/fta/fta-utils.ts rename to extension/src-language-server/fta/diagram/utils.ts index 0c601a9..59bd71c 100644 --- a/extension/src-language-server/fta/fta-utils.ts +++ b/extension/src-language-server/fta/diagram/utils.ts @@ -16,20 +16,34 @@ */ import { AstNode } from "langium"; -import { IdCache } from "langium-sprotty"; -import { - Gate, - isAND, - isComponent, - isCondition, - isGate, - isInhibitGate, - isKNGate, - isOR, - isTopEvent, -} from "../generated/ast"; +import { isTopEvent, isComponent, isCondition, isGate, isAND, isOR, isKNGate, isInhibitGate, Gate } from "../../generated/ast"; import { FTNodeType } from "./fta-model"; +/** + * Getter for the type of a FTA component. + * @param node AstNode which type should be determined. + * @returns the type of {@code node}. + */ +export function getFTNodeType(node: AstNode): FTNodeType { + if (isTopEvent(node)) { + return FTNodeType.TOPEVENT; + } else if (isComponent(node)) { + return FTNodeType.COMPONENT; + } else if (isCondition(node)) { + return FTNodeType.CONDITION; + } else if (isGate(node) && isAND(node.type)) { + return FTNodeType.AND; + } else if (isGate(node) && isOR(node.type)) { + return FTNodeType.OR; + } else if (isGate(node) && isKNGate(node.type)) { + return FTNodeType.KN; + } else if (isGate(node) && isInhibitGate(node.type)) { + return FTNodeType.INHIBIT; + } + return FTNodeType.UNDEFINED; +} + + /** * Getter for the references contained in {@code node}. * @param node The AstNode we want the children of. @@ -60,29 +74,7 @@ export function getTargets(node: AstNode): AstNode[] { return targets; } -/** - * Getter for the type of a FTA component. - * @param node AstNode which type should be determined. - * @returns the type of {@code node}. - */ -export function getFTNodeType(node: AstNode): FTNodeType { - if (isTopEvent(node)) { - return FTNodeType.TOPEVENT; - } else if (isComponent(node)) { - return FTNodeType.COMPONENT; - } else if (isCondition(node)) { - return FTNodeType.CONDITION; - } else if (isGate(node) && isAND(node.type)) { - return FTNodeType.AND; - } else if (isGate(node) && isOR(node.type)) { - return FTNodeType.OR; - } else if (isGate(node) && isKNGate(node.type)) { - return FTNodeType.KN; - } else if (isGate(node) && isInhibitGate(node.type)) { - return FTNodeType.INHIBIT; - } - return FTNodeType.UNDEFINED; -} + /** Sorts every gate with its type and puts them into a two dimensional array * @param gates Every gate within the FTAModel @@ -113,56 +105,4 @@ export function getAllGateTypes(gates: Gate[]): Map { allGates.set("KNGate", kNGates); allGates.set("InhibitGate", inhibGates); return allGates; -} - -/** - * Takes all cut sets and returns a string that resembles it, so it can be displayed in the console. - * @param cutSets The cut sets of the current Fault Tree. - * @param idCache The idCache of the generator context from the current graph. - * @returns a string that contains every cut set. - */ -export function cutSetToString(cutSets: Set[], idCache: IdCache): string { - const result = "The resulting " + cutSets.length + " cut sets are: \n"; - return setToString(cutSets, idCache, result); -} - -/** - * Takes all minimal cut sets and returns a string that resembles it, so it can be displayed in the console. - * @param minimalCutSets The minimal cut sets of the current Fault Tree. - * @param idCache The idCache of the generator context from the current graph. - * @returns a string that contains every minimal cut set. - */ -export function minimalCutSetToString(minimalCutSets: Set[], idCache: IdCache): string { - const result = "The resulting " + minimalCutSets.length + " minimal cut sets are: \n"; - return setToString(minimalCutSets, idCache, result); -} - -/** - * Takes all (minimal) cut sets and returns a string that resembles it, so it can be displayed in the console. - * @param cutSets The (minimal) cut sets of the current Fault Tree. - * @returns A string that resembles the cut sets. - */ -export function setToString(cutSets: Set[], idCache: IdCache, result: string): string { - result += "["; - - for (const set of cutSets) { - result += "["; - set.forEach(element => result+=idCache.getId(element)+", "); - // for (const element of set) { - // if (set.indexOf(element) === set.length - 1) { - // result += idCache.getId(element); - // } else { - // result = result + idCache.getId(element) + ","; - // } - // } - result += "]"; - result += "\n"; - // if (cutSets.indexOf(set) === cutSets.length - 1) { - // result += "]\n"; - // } else { - // result += ",\n"; - // } - } - - return result; -} +} \ No newline at end of file diff --git a/extension/src-language-server/fta/fta-cutSet-generator.ts b/extension/src-language-server/fta/fta-cutSet-generator.ts deleted file mode 100644 index 0f81528..0000000 --- a/extension/src-language-server/fta/fta-cutSet-generator.ts +++ /dev/null @@ -1,267 +0,0 @@ -/* - * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient - * - * http://rtsys.informatik.uni-kiel.de/kieler - * - * Copyright 2023 by - * + Kiel University - * + Department of Computer Science - * + Real-Time and Embedded Systems Group - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - */ - -import { AstNode } from "langium"; -import { IdCache } from "langium-sprotty"; -import { - TopEvent, - isAND, - isComponent, - isCondition, - isGate, - isInhibitGate, - isKNGate, - isOR, - isTopEvent, -} from "../generated/ast"; - -export class CutSetGenerator { - /** - * Takes the Fault Tree and returns a two-dimensional array of AstNodes where every inner list resembles a minimal - * cut set. - * @param allNodes All Nodes in the fault tree. - * @param idCache The idCache of the generator context from the corresponding graph. - * @returns A list of lists that that contains every minimal cut set of the given Fault Tree. - */ - determineMinimalCutSet(allNodes: AstNode[], idCache: IdCache): Set[] { - // TODO: add minimal flag (could reduce computation cost) - const allCutSets = this.generateCutSetsForFT(allNodes, idCache); - - // Cut sets are minimal if removing one element destroys the cut set - // If cut set contains another cut set from the array, remove it since it is not minimal - const minimalCutSet = allCutSets.filter((cutSet) => this.checkIfMinimalCutSet(cutSet, allCutSets)); - - return minimalCutSet; - } - - /** - * Checks whether the given list {@code innerList} is a minimal cut set. - * @param cutSet The list to check. - * @param allCutSets All Cut Sets of the Fault Tree. - * @returns True if the given list is a minimal cut set. - */ - protected checkIfMinimalCutSet(cutSet: Set, allCutSets: Set[]): boolean { - for (const otherCutSet of allCutSets) { - let contained = true; - otherCutSet.forEach((element) => { - if (!cutSet.has(element)) { - contained = false; - } - }); - if (contained && cutSet !== otherCutSet) { - return false; - } - } - return true; - } - - /** - * Determines all cut sets of a fault tree. - * @param allNodes All nodes of the fault tree. - * @param idCache The idCache of the generator context from the corresponding graph. - * @returns A list of lists that contains every cut set of the given Fault Tree. - */ - generateCutSetsForFT(allNodes: AstNode[], idCache: IdCache): Set[] { - /* Idea: - Start from the top event. - Get the only child of top event (will always be only one) as our starting node. - Calculate all children of the node and evaluate them. - In the evaluation we check if the child has children too and do the same recursively until - the children are components. - Depending on the type of the node process the results of the children differently. */ - - const startNode = this.getChildOfTopEvent(allNodes); - if (startNode) { - // determine the cut sets of the Fault Tree - return this.determineCutSetsForGate(startNode, allNodes, idCache); - } - return []; - } - - /** - * Determines the cut sets for the (sub) fault tree that has {@code startNode} as the top node. - * @param startNode The top node of the (sub) fault tree for which the cut sets should be determined. - * @param allNodes All nodes of the fault tree. - * @param idCache The idCache of the generator context from the corresponding graph. - * @returns the determined cut sets for the (sub) fault tree as a list of lists. - */ - protected determineCutSetsForGate( - startNode: AstNode, - allNodes: AstNode[], - idCache: IdCache - ): Set[] { - let result: Set[] = []; - - // components do not have children, so return the component - if (isComponent(startNode) || isCondition(startNode)) { - return [new Set([startNode])]; - } - - const children = this.getChildrenOfNode(startNode); - if (children.length === 0 || !isGate(startNode)) { - return result; - } - - if (isAND(startNode.type) || isInhibitGate(startNode.type)) { - // concatenate each cut set of a child with every cut set of the other children - for (const child of children) { - result = this.concatInnerListsWithEachOther( - this.determineCutSetsForGate(child, allNodes, idCache), - result - ); - } - } else if (isOR(startNode.type)) { - // add the cut sets of each child to the result - for (const child of children) { - result.push(...this.determineCutSetsForGate(child, allNodes, idCache)); - } - } else if (isKNGate(startNode.type)) { - const k = startNode.type.k; - const n = startNode.type.children.length; - - // determine every combination (with length k or greater) of the children - // Example: children = [M1, M2, G1] and k = 2 -> [[M1, M2], [M1, G1], [M2, G1], [M1, M2, G1]] - const combinations: AstNode[][] = []; - for (let i = k; i <= n; i++) { - combinations.push(...this.createAllCombinations(children, i)); - } - - // determine the cut sets for each combination - // (treat each combination the same way as an AND gate with the combination as its children) - for (const combination of combinations) { - let intermediateResult: Set[] = []; - for (const element of combination) { - intermediateResult = this.concatInnerListsWithEachOther( - this.determineCutSetsForGate(element, allNodes, idCache), - intermediateResult - ); - } - result.push(...intermediateResult); - } - } - - return result; - } - - /** - * Create all combinations with length {@code k} of the given {@code nodes}. - * @param nodes The list of nodes for which all combinations should be created. - * @param k The number of elements in a combination. - * @returns all combinations with length {@code k} of the given {@ode ndoes}. - */ - protected createAllCombinations(nodes: AstNode[], k: number): AstNode[][] { - const combinations: AstNode[][] = []; - - if (k > nodes.length || k <= 0) { - return []; - } - if (k === nodes.length) { - return [nodes]; - } - if (k === 1) { - nodes.forEach((node) => combinations.push([node])); - } - - for (let i = 0; i < nodes.length; i++) { - const currentNode: AstNode = nodes[i]; - for (const subElements of this.createAllCombinations(nodes.slice(i + 1), k - 1)) { - subElements.unshift(currentNode); - combinations.push(subElements); - } - } - - return combinations; - } - - /** - * Determines all the children of {@code node}. - * @param node The node for which the children should be determined. - * @returns all children of the given {@code node}. - */ - protected getChildrenOfNode(node: AstNode): AstNode[] { - const children: AstNode[] = []; - if (isComponent(node) || isCondition(node)) { - // node has no children - return children; - } - - if (isGate(node)) { - // add children of the gate - for (const childRef of node.type.children) { - if (childRef.ref) { - children.push(childRef.ref); - } - } - // add condition of inhibit gate - if (isInhibitGate(node.type)) { - for (const childRef of node.type.condition) { - if (childRef?.ref) { - children.push(childRef.ref); - } - } - } - } - return children; - } - - /** - * Determines the child of the top event. - * @param allNodes All nodes in the fault tree. - * @returns the child of the top event. - */ - protected getChildOfTopEvent(allNodes: AstNode[]): AstNode | undefined { - const topEventChildren = (allNodes.find((node) => isTopEvent(node)) as TopEvent).children; - if (topEventChildren.length !== 0) { - return topEventChildren[0].ref; - } - } - - /** - * Concatenates the cut sets of two sets with each other. - * E.g. [X1, X2] and [Y1, Y2] result in [[X1, Y1], [X1, Y2], [X2, Y1], [X2, Y2]]. - * @param firstAllCutSets The first set of cut sets. - * @param secondAllCutSets The second set of cut sets. - * @returns a set where every cut set of both sets is concatenated with each other. - */ - protected concatInnerListsWithEachOther( - firstAllCutSets: Set[], - secondAllCutSets: Set[] - ): Set[] { - const result: Set[] = []; - // if one cut set set is empty, return the other - if (firstAllCutSets.length === 0) { - return secondAllCutSets; - } - if (secondAllCutSets.length === 0) { - return firstAllCutSets; - } - // concatenate sets - for (const firstCutSet of firstAllCutSets) { - for (const secondCutSet of secondAllCutSets) { - // add the new set if it snot already included in the result - const newSet = new Set([...firstCutSet, ...secondCutSet]); - if (result.every((cutSet) => !eqSet(cutSet, newSet))) { - result.push(newSet); - } - } - } - return result; - } -} - -/** Checks whether two sets of AstNodes are equal */ -const eqSet = (xs: Set, ys: Set): boolean => xs.size === ys.size && [...xs].every((x) => ys.has(x)); diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index c7a62d9..6d360f7 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -16,9 +16,10 @@ */ import { Connection } from "vscode-languageserver"; -import { FtaDiagramGenerator } from "./fta-diagram-generator"; +import { FtaDiagramGenerator } from "./diagram/fta-diagram-generator"; import { FtaServices } from "./fta-module"; -import { cutSetToString, minimalCutSetToString } from "./fta-utils"; +import { cutSetsToString } from "./utils"; +import { determineMinimalCutSet, generateCutSetsForFT } from "./analysis/fta-cutSet-calculator"; /** * Adds handlers for notifications regarding fta. @@ -38,21 +39,19 @@ function addCutSetsHandler(connection: Connection, ftaServices: FtaServices): vo connection.onRequest("generate/getCutSets", () => { const diagramGenerator = ftaServices.diagram.DiagramGenerator as FtaDiagramGenerator; const nodes = diagramGenerator.getNodes(); - const idCache = diagramGenerator.getCache(); - const cutSets = ftaServices.bdd.Bdd.generateCutSetsForFT(nodes, idCache); - const cutSetsToString = cutSetToString(cutSets, idCache); + const cutSets = generateCutSetsForFT(nodes); + const cutSetText = cutSetsToString(cutSets); - return cutSetsToString; + return cutSetText; }); connection.onRequest("generate/getMinimalCutSets", () => { const diagramGenerator = ftaServices.diagram.DiagramGenerator as FtaDiagramGenerator; const nodes = diagramGenerator.getNodes(); - const idCache = diagramGenerator.getCache(); - const minimalCutSets = ftaServices.bdd.Bdd.determineMinimalCutSet(nodes, idCache); - const minCutSetToString = minimalCutSetToString(minimalCutSets, idCache); + const minimalCutSets = determineMinimalCutSet(nodes); + const minCutSetToString = cutSetsToString(minimalCutSets, true); return minCutSetToString; }); diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index c5895fc..5fdd444 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -25,14 +25,13 @@ import { IElementFilter, ILayoutConfigurator, } from "sprotty-elk/lib/elk-layout"; -import { CutSetGenerator } from "./fta-cutSet-generator"; -import { FtaDiagramGenerator } from "./fta-diagram-generator"; -import { FtaLayoutConfigurator } from "./fta-layout-config"; +import { FtaDiagramGenerator } from "./diagram/fta-diagram-generator"; +import { FtaLayoutConfigurator } from "./diagram/fta-layout-config"; import { FtaValidationRegistry, FtaValidator } from "./fta-validator"; import { FtaSynthesisOptions } from "./synthesis-options"; /** - * Declaration of custom services - add your own service classes here. + * Declaration of custom services. */ export type FtaAddedServices = { validation: { @@ -46,9 +45,6 @@ export type FtaAddedServices = { options: { SynthesisOptions: FtaSynthesisOptions; }; - bdd: { - Bdd: CutSetGenerator; - }; }; /** @@ -84,7 +80,4 @@ export const FtaModule: Module new FtaSynthesisOptions(), }, - bdd: { - Bdd: () => new CutSetGenerator(), - }, }; diff --git a/extension/src-language-server/fta/utils.ts b/extension/src-language-server/fta/utils.ts new file mode 100644 index 0000000..e9633cb --- /dev/null +++ b/extension/src-language-server/fta/utils.ts @@ -0,0 +1,36 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { Component, Condition, Gate, TopEvent } from "../generated/ast"; + +export type namedFtaElement = Component | Condition | Gate | TopEvent; + +/** + * Translates the given {@code cutSets} to a string. + * @param cutSets The cut sets to translate. + * @param minimal Determines whether the given {@code cutSets} are minimal. + * @returns a string that contains every cut set. + */ +export function cutSetsToString(cutSets: Set[], minimal?: boolean): string { + let text = `The resulting ${cutSets.length}`; + if (minimal) { + text += ` minimal`; + } + text += ` cut sets are:\n`; + text += `[${cutSets.map((cutSet) => `[${[...cutSet].map((element) => element.name).join(", ")}]`).join(",\n")}]`; + return text; +} diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts index 6f0cf6b..be9b55a 100644 --- a/extension/src-webview/di.config.ts +++ b/extension/src-webview/di.config.ts @@ -40,7 +40,7 @@ import { import { SvgCommand } from "./actions"; import { SvgPostprocessor } from "./exportPostProcessor"; import { CustomSvgExporter } from "./exporter"; -import { FTAEdge, FTANode, FTA_EDGE_TYPE, FTA_NODE_TYPE, TREE_TYPE } from './fta-model'; +import { FTAEdge, FTANode, FTA_EDGE_TYPE, FTA_NODE_TYPE } from './fta-model'; import { FTAGraphView, FTANodeView, PolylineArrowEdgeViewFTA } from './fta-views'; import { StpaModelViewer } from "./model-viewer"; import { optionsModule } from "./options/options-module"; @@ -102,7 +102,6 @@ const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => configureModelElement(context, 'pre-rendered', PreRenderedElement, PreRenderedView); //FTA - configureModelElement(context, TREE_TYPE, SNode, FTAGraphView); configureModelElement(context, FTA_EDGE_TYPE, FTAEdge, PolylineArrowEdgeViewFTA); configureModelElement(context, FTA_NODE_TYPE, FTANode, FTANodeView); }); diff --git a/extension/src-webview/fta-model.ts b/extension/src-webview/fta-model.ts index bc579b8..631e01b 100644 --- a/extension/src-webview/fta-model.ts +++ b/extension/src-webview/fta-model.ts @@ -26,13 +26,12 @@ import { selectFeature, } from "sprotty"; -// The types of diagram elements +/* fault tree element types */ export const FTA_NODE_TYPE = "node:fta"; -export const TREE_TYPE = "node:tree"; export const FTA_EDGE_TYPE = "edge:fta"; /** - * Node representing a FTA component. + * Node of a fault tree. */ export class FTANode extends SNode { static readonly DEFAULT_FEATURES = [ @@ -52,14 +51,14 @@ export class FTANode extends SNode { } /** - * Edge representing an edge in the fault tree. + * Edge of a fault tree. */ export class FTAEdge extends SEdge { highlight?: boolean; } /** - * The different types of nodes of FTA. + * Types of fault tree nodes. */ export enum FTNodeType { TOPEVENT, From a2f5cad04226ee619e843fa7315a2e52ba87b7ab Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Thu, 21 Sep 2023 15:46:09 +0200 Subject: [PATCH 42/61] refactored commands --- extension/langium-config.json | 4 +- extension/package.json | 82 +++++++++---------- .../fta/diagram/fta-diagram-generator.ts | 15 +--- .../src-language-server/{ => fta}/fta.langium | 0 .../{ => stpa}/stpa.langium | 0 extension/src-webview/di.config.ts | 10 +-- extension/src/extension.ts | 16 ++-- 7 files changed, 58 insertions(+), 69 deletions(-) rename extension/src-language-server/{ => fta}/fta.langium (100%) rename extension/src-language-server/{ => stpa}/stpa.langium (100%) diff --git a/extension/langium-config.json b/extension/langium-config.json index c8c1fe8..f8fc33b 100644 --- a/extension/langium-config.json +++ b/extension/langium-config.json @@ -2,14 +2,14 @@ "projectName": "Pasta", "languages": [{ "id": "stpa", - "grammar": "src-language-server/stpa.langium", + "grammar": "src-language-server/stpa/stpa.langium", "fileExtensions": [".stpa"], "textMate": { "out": "syntaxes/stpa.tmLanguage.json" } },{ "id": "fta" , - "grammar": "src-language-server/fta.langium", + "grammar": "src-language-server/fta/fta.langium", "fileExtensions": [".fta"], "textMate": { "out": "syntaxes/fta.tmLanguage.json" diff --git a/extension/package.json b/extension/package.json index 70635b9..225afbc 100644 --- a/extension/package.json +++ b/extension/package.json @@ -115,40 +115,40 @@ { "command": "pasta.diagram.fit", "title": "Fit to Screen", - "category": "STPA Diagram" + "category": "PASTA Diagram" }, { "command": "pasta.diagram.center", "title": "Center selection", - "category": "STPA Diagram" + "category": "PASTA Diagram" }, { "command": "pasta.diagram.delete", "title": "Delete selected element", - "category": "STPA Diagram" + "category": "PASTA Diagram" }, { "command": "pasta.diagram.export", "title": "Export diagram to SVG", - "category": "STPA Diagram" + "category": "PASTA Diagram" }, { - "command": "pasta.checks.setCheckResponsibilitiesForConstraints", + "command": "pasta.stpa.checks.setCheckResponsibilitiesForConstraints", "title": "Set the responsibilities for constraints check", "category": "STPA Checks" }, { - "command": "pasta.checks.checkConstraintsForUCAs", + "command": "pasta.stpa.checks.checkConstraintsForUCAs", "title": "Set the constraints for UCA check", "category": "STPA Checks" }, { - "command": "pasta.checks.checkScenariosForUCAs", + "command": "pasta.stpa.checks.checkScenariosForUCAs", "title": "Set the scenarios for UCA check", "category": "STPA Checks" }, { - "command": "pasta.checks.checkSafetyRequirementsForUCAs", + "command": "pasta.stpa.checks.checkSafetyRequirementsForUCAs", "title": "Set the safety requirements for UCA check", "category": "STPA Checks" }, @@ -163,22 +163,22 @@ "category": "STPA ID Enforcement" }, { - "command": "pasta.SBM.generation", + "command": "pasta.stpa.SBM.generation", "title": "Generate Safe Behavioral Model (SBM)", "category": "STPA SBM" }, { - "command": "pasta.md.creation", + "command": "pasta.stpa.md.creation", "title": "Create a Markdown file", "category": "STPA PDF Creation" }, { - "command": "pasta.generate.ftaCutSets", + "command": "pasta.fta.cutSets", "title": "Generate the cut sets", "category": "Cut Sets" }, { - "command": "pasta.generate.ftaMinimalCutSets", + "command": "pasta.fta.minimalCutSets", "title": "Generate the minimal cut sets", "category": "Cut Sets" } @@ -199,50 +199,50 @@ }, { "command": "pasta.diagram.fit", - "when": "stpa-diagram-focused" + "when": "pasta-diagram-focused" }, { "command": "pasta.diagram.center", - "when": "stpa-diagram-focused" + "when": "pasta-diagram-focused" }, { "command": "pasta.diagram.delete", - "when": "stpa-diagram-focused" + "when": "pasta-diagram-focused" }, { "command": "pasta.diagram.export", - "when": "stpa-diagram-focused" + "when": "pasta-diagram-focused" }, { - "command": "pasta.checks.setCheckResponsibilitiesForConstraints", + "command": "pasta.stpa.checks.setCheckResponsibilitiesForConstraints", "when": "editorLangId == 'stpa'" }, { - "command": "pasta.checks.checkConstraintsForUCAs", + "command": "pasta.stpa.checks.checkConstraintsForUCAs", "when": "editorLangId == 'stpa'" }, { - "command": "pasta.checks.checkScenariosForUCAs", + "command": "pasta.stpa.checks.checkScenariosForUCAs", "when": "editorLangId == 'stpa'" }, { - "command": "pasta.checks.checkSafetyRequirementsForUCAs", + "command": "pasta.stpa.checks.checkSafetyRequirementsForUCAs", "when": "editorLangId == 'stpa'" }, { - "command": "pasta.SBM.generation", + "command": "pasta.stpa.SBM.generation", "when": "editorLangId == 'stpa'" }, { - "command": "pasta.md.creation", + "command": "pasta.stpa.md.creation", "when": "editorLangId == 'stpa'" }, { - "command": "pasta.generate.ftaCutSets", + "command": "pasta.fta.cutSets", "when": "editorLangId == 'fta'" }, { - "command": "pasta.generate.ftaMinimalCutSets", + "command": "pasta.fta.minimalCutSets", "when": "editorLangId == 'fta'" } ], @@ -258,7 +258,7 @@ "group": "navigation" }, { - "submenu": "pasta.checks", + "submenu": "pasta.stpa.checks", "group": "checks" }, { @@ -266,46 +266,46 @@ "group": "generate" }, { - "command": "pasta.SBM.generation", + "command": "pasta.stpa.SBM.generation", "when": "editorLangId == 'stpa'", "group": "stpa" }, { - "command": "pasta.md.creation", + "command": "pasta.stpa.md.creation", "when": "editorLangId == 'stpa'", "group": "stpa" } ], - "pasta.checks": [ + "pasta.stpa.checks": [ { - "command": "pasta.checks.setCheckResponsibilitiesForConstraints", + "command": "pasta.stpa.checks.setCheckResponsibilitiesForConstraints", "title": "editorLangId == 'stpa'", "group": "navigation" }, { - "command": "pasta.checks.checkConstraintsForUCAs", + "command": "pasta.stpa.checks.checkConstraintsForUCAs", "title": "editorLangId == 'stpa'", "group": "navigation" }, { - "command": "pasta.checks.checkScenariosForUCAs", + "command": "pasta.stpa.checks.checkScenariosForUCAs", "title": "editorLangId == 'stpa'", "group": "navigation" }, { - "command": "pasta.checks.checkSafetyRequirementsForUCAs", + "command": "pasta.stpa.checks.checkSafetyRequirementsForUCAs", "title": "editorLangId == 'stpa'", "group": "navigation" } ], "pasta.generate": [ { - "command": "pasta.generate.ftaCutSets", + "command": "pasta.fta.cutSets", "when": "editorLangId == 'fta'", "group": "navigation" }, { - "command": "pasta.generate.ftaMinimalCutSets", + "command": "pasta.fta.minimalCutSets", "when": "editorLangId == 'fta'", "group": "navigation" } @@ -334,12 +334,12 @@ "group": "navigation" }, { - "command": "pasta.generate.ftaCutSets", + "command": "pasta.fta.cutSets", "when": "resourceExtname == '.fta'", "group": "navigation" }, { - "command": "pasta.generate.ftaMinimalCutSets", + "command": "pasta.fta.minimalCutSets", "when": "resourceExtname == '.fta'", "group": "navigation" } @@ -347,7 +347,7 @@ }, "submenus": [ { - "id": "pasta.checks", + "id": "pasta.stpa.checks", "label": "Validation Checks" }, { @@ -360,25 +360,25 @@ "key": "alt+f", "mac": "alt+f", "command": "pasta.diagram.fit", - "when": "stpa-diagram-focused" + "when": "pasta-diagram-focused" }, { "key": "alt+c", "mac": "alt+c", "command": "pasta.diagram.center", - "when": "stpa-diagram-focused" + "when": "pasta-diagram-focused" }, { "key": "alt+e", "mac": "alt+e", "command": "pasta.diagram.export", - "when": "stpa-diagram-focused" + "when": "pasta-diagram-focused" }, { "key": "delete", "mac": "delete", "command": "pasta.diagram.delete", - "when": "stpa-diagram-focused" + "when": "pasta-diagram-focused" }, { "key": "ctrl+z", diff --git a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts index a1f8c9e..c620560 100644 --- a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts @@ -16,7 +16,7 @@ */ import { AstNode } from "langium"; -import { GeneratorContext, IdCache, LangiumDiagramGenerator } from "langium-sprotty"; +import { GeneratorContext, LangiumDiagramGenerator } from "langium-sprotty"; import { SLabel, SModelElement, SModelRoot } from "sprotty-protocol"; import { Component, @@ -29,14 +29,13 @@ import { isGate, isKNGate, } from "../../generated/ast"; +import { FtaServices } from "../fta-module"; import { FTAEdge, FTANode } from "./fta-interfaces"; import { FTA_EDGE_TYPE, FTA_NODE_TYPE } from "./fta-model"; -import { FtaServices } from "../fta-module"; import { getAllGateTypes, getFTNodeType, getTargets } from "./utils"; export class FtaDiagramGenerator extends LangiumDiagramGenerator { allNodes: AstNode[]; - idCache: IdCache; constructor(services: FtaServices) { super(services); } @@ -83,8 +82,6 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { this.allNodes = this.allNodes.concat(...value); }); - this.idCache = args.idCache; - return { type: "graph", id: "root", @@ -100,14 +97,6 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { return this.allNodes; } - /** - * Getter method for the idCache to get the ids for every node. - * @returns the idCache of generator context. - */ - public getCache(): IdCache { - return this.idCache; - } - /** * Generates the edges for {@code node}. * @param node FTA component for which the edges should be created. diff --git a/extension/src-language-server/fta.langium b/extension/src-language-server/fta/fta.langium similarity index 100% rename from extension/src-language-server/fta.langium rename to extension/src-language-server/fta/fta.langium diff --git a/extension/src-language-server/stpa.langium b/extension/src-language-server/stpa/stpa.langium similarity index 100% rename from extension/src-language-server/stpa.langium rename to extension/src-language-server/stpa/stpa.langium diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts index be9b55a..5471be9 100644 --- a/extension/src-webview/di.config.ts +++ b/extension/src-webview/di.config.ts @@ -40,8 +40,8 @@ import { import { SvgCommand } from "./actions"; import { SvgPostprocessor } from "./exportPostProcessor"; import { CustomSvgExporter } from "./exporter"; -import { FTAEdge, FTANode, FTA_EDGE_TYPE, FTA_NODE_TYPE } from './fta-model'; -import { FTAGraphView, FTANodeView, PolylineArrowEdgeViewFTA } from './fta-views'; +import { FTAEdge, FTANode, FTA_EDGE_TYPE, FTA_NODE_TYPE } from "./fta-model"; +import { FTANodeView, PolylineArrowEdgeViewFTA } from "./fta-views"; import { StpaModelViewer } from "./model-viewer"; import { optionsModule } from "./options/options-module"; import { sidebarModule } from "./sidebar"; @@ -98,9 +98,9 @@ const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => configureModelElement(context, STPA_INTERMEDIATE_EDGE_TYPE, STPAEdge, IntermediateEdgeView); configureModelElement(context, CS_EDGE_TYPE, CSEdge, PolylineArrowEdgeView); configureModelElement(context, STPA_PORT_TYPE, STPAPort, PortView); - configureModelElement(context, 'html', HtmlRoot, HtmlRootView); - configureModelElement(context, 'pre-rendered', PreRenderedElement, PreRenderedView); - + configureModelElement(context, "html", HtmlRoot, HtmlRootView); + configureModelElement(context, "pre-rendered", PreRenderedElement, PreRenderedView); + //FTA configureModelElement(context, FTA_EDGE_TYPE, FTAEdge, PolylineArrowEdgeViewFTA); configureModelElement(context, FTA_NODE_TYPE, FTANode, FTANodeView); diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 0d07af4..b74d092 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -109,22 +109,22 @@ function registerSTPACommands(manager: StpaLspVscodeExtension, context: vscode.E ); // commands for toggling the provided validation checks context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.checks.setCheckResponsibilitiesForConstraints', async () => { + vscode.commands.registerCommand(options.extensionPrefix + '.stpa.checks.setCheckResponsibilitiesForConstraints', async () => { createQuickPickForWorkspaceOptions("checkResponsibilitiesForConstraints"); }) ); context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.checks.checkConstraintsForUCAs', async () => { + vscode.commands.registerCommand(options.extensionPrefix + '.stpa.checks.checkConstraintsForUCAs', async () => { createQuickPickForWorkspaceOptions("checkConstraintsForUCAs"); }) ); context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.checks.checkScenariosForUCAs', async () => { + vscode.commands.registerCommand(options.extensionPrefix + '.stpa.checks.checkScenariosForUCAs', async () => { createQuickPickForWorkspaceOptions("checkScenariosForUCAs"); }) ); context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.checks.checkSafetyRequirementsForUCAs', async () => { + vscode.commands.registerCommand(options.extensionPrefix + '.stpa.checks.checkSafetyRequirementsForUCAs', async () => { createQuickPickForWorkspaceOptions("checkSafetyRequirementsForUCAs"); }) ); @@ -143,14 +143,14 @@ function registerSTPACommands(manager: StpaLspVscodeExtension, context: vscode.E // command for creating a pdf context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.md.creation', async (uri: vscode.Uri) => { + vscode.commands.registerCommand(options.extensionPrefix + '.stpa.md.creation', async (uri: vscode.Uri) => { const data: StpaResult = await languageClient.sendRequest('result/getData', uri.toString()); await createSTPAResultMarkdownFile(data, manager); }) ); context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.SBM.generation', async (uri: vscode.Uri) => { + vscode.commands.registerCommand(options.extensionPrefix + '.stpa.SBM.generation', async (uri: vscode.Uri) => { await manager.lsReady; const formulas: Record = await languageClient.sendRequest('verification/generateLTL', uri.path); // controlAction names are just the action without the controller as prefix @@ -173,7 +173,7 @@ function registerSTPACommands(manager: StpaLspVscodeExtension, context: vscode.E // commands for computing and displaying the (minimal) cut sets of the fault tree. context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.generate.ftaCutSets', async () =>{ + vscode.commands.registerCommand(options.extensionPrefix + '.fta.cutSets', async () =>{ const cutSets:string = await languageClient.sendRequest('generate/getCutSets'); //Send cut sets to webview to display them in a dropdown menu. dispatchCutSetsToWebview(manager, cutSets); @@ -181,7 +181,7 @@ function registerSTPACommands(manager: StpaLspVscodeExtension, context: vscode.E }) ); context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.generate.ftaMinimalCutSets', async () =>{ + vscode.commands.registerCommand(options.extensionPrefix + '.fta.minimalCutSets', async () =>{ const minimalCutSets:string = await languageClient.sendRequest('generate/getMinimalCutSets'); dispatchCutSetsToWebview(manager, minimalCutSets); createOutputChannel(minimalCutSets, "FTA Cut Sets"); From 204db0e80db2c29bd4b4d9f1374dbbdf188e41f3 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Thu, 21 Sep 2023 15:54:59 +0200 Subject: [PATCH 43/61] adjusted grammar --- .../fta/analysis/fta-cutSet-calculator.ts | 16 +++++++------- .../fta/diagram/fta-diagram-generator.ts | 9 ++++---- .../src-language-server/fta/diagram/utils.ts | 22 +++++++++---------- extension/src-language-server/fta/fta.langium | 10 ++++----- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts b/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts index fa8ec09..a2926b7 100644 --- a/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts +++ b/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts @@ -108,19 +108,19 @@ function determineCutSetsForGate(startNode: AstNode, allNodes: AstNode[]): Set [[M1, M2], [M1, G1], [M2, G1], [M1, M2, G1]] @@ -190,14 +190,14 @@ function getChildrenOfNode(node: AstNode): namedFtaElement[] { if (isGate(node)) { // add children of the gate - for (const childRef of node.type.children) { + for (const childRef of node.children) { if (childRef.ref) { children.push(childRef.ref); } } // add condition of inhibit gate - if (isInhibitGate(node.type)) { - for (const childRef of node.type.condition) { + if (isInhibitGate(node)) { + for (const childRef of node.condition) { if (childRef?.ref) { children.push(childRef.ref); } diff --git a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts index c620560..5b8b2e3 100644 --- a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts @@ -26,8 +26,7 @@ import { TopEvent, isComponent, isCondition, - isGate, - isKNGate, + isKNGate } from "../../generated/ast"; import { FtaServices } from "../fta-module"; import { FTAEdge, FTANode } from "./fta-interfaces"; @@ -180,7 +179,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { desc = node.description; } - if (isGate(node) && isKNGate(node.type)) { + if (isKNGate(node)) { return { type: FTA_NODE_TYPE, id: nodeId, @@ -188,8 +187,8 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { description: desc, children: children, highlight: true, - k: node.type.k, - n: node.type.children.length, + k: node.k, + n: node.children.length, layout: "stack", layoutOptions: { paddingTop: 10.0, diff --git a/extension/src-language-server/fta/diagram/utils.ts b/extension/src-language-server/fta/diagram/utils.ts index 59bd71c..857db59 100644 --- a/extension/src-language-server/fta/diagram/utils.ts +++ b/extension/src-language-server/fta/diagram/utils.ts @@ -31,13 +31,13 @@ export function getFTNodeType(node: AstNode): FTNodeType { return FTNodeType.COMPONENT; } else if (isCondition(node)) { return FTNodeType.CONDITION; - } else if (isGate(node) && isAND(node.type)) { + } else if (isAND(node)) { return FTNodeType.AND; - } else if (isGate(node) && isOR(node.type)) { + } else if (isOR(node)) { return FTNodeType.OR; - } else if (isGate(node) && isKNGate(node.type)) { + } else if (isKNGate(node)) { return FTNodeType.KN; - } else if (isGate(node) && isInhibitGate(node.type)) { + } else if (isInhibitGate(node)) { return FTNodeType.INHIBIT; } return FTNodeType.UNDEFINED; @@ -58,13 +58,13 @@ export function getTargets(node: AstNode): AstNode[] { } } } else if (isGate(node)) { - for (const ref of node.type.children) { + for (const ref of node.children) { if (ref?.ref) { targets.push(ref.ref); } } - if (isInhibitGate(node.type)) { - for (const ref of node.type.condition) { + if (isInhibitGate(node)) { + for (const ref of node.condition) { if (ref?.ref) { targets.push(ref.ref); } @@ -89,13 +89,13 @@ export function getAllGateTypes(gates: Gate[]): Map { const inhibGates: AstNode[] = []; for (const gate of gates) { - if (isAND(gate.type)) { + if (isAND(gate)) { andGates.push(gate); - } else if (isOR(gate.type)) { + } else if (isOR(gate)) { orGates.push(gate); - } else if (isKNGate(gate.type)) { + } else if (isKNGate(gate)) { kNGates.push(gate); - } else if (isInhibitGate(gate.type)) { + } else if (isInhibitGate(gate)) { inhibGates.push(gate); } } diff --git a/extension/src-language-server/fta/fta.langium b/extension/src-language-server/fta/fta.langium index e288492..40ae394 100644 --- a/extension/src-language-server/fta/fta.langium +++ b/extension/src-language-server/fta/fta.langium @@ -13,7 +13,7 @@ Condition: name=ID description=STRING; Gate: - name=ID "=" type=(AND|OR|KNGate|InhibitGate); + AND|OR|KNGate|InhibitGate; TopEvent: name=STRING "=" children+=[Children:ID]; @@ -22,16 +22,16 @@ Children: Gate | Component; AND: - children+=[Children:ID] ('and' children+=[Children:ID])+; + name=ID "=" children+=[Children:ID] ('and' children+=[Children:ID])+; OR: - children+=[Children:ID] ('or' children+=[Children:ID])+; + name=ID "=" children+=[Children:ID] ('or' children+=[Children:ID])+; KNGate: - k=INT 'of' children+=[Children:ID] (',' children+=[Children:ID])+; + name=ID "=" k=INT 'of' children+=[Children:ID] (',' children+=[Children:ID])+; InhibitGate: - condition+=[Condition:ID] 'inhibits' children+=[Children:ID]; + name=ID "=" condition+=[Condition:ID] 'inhibits' children+=[Children:ID]; hidden terminal WS: /\s+/; terminal ID: /[_a-zA-Z][\w_]*/; From 2219643e975ed5ac02b0255b9649763c55813f7d Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Thu, 21 Sep 2023 17:07:46 +0200 Subject: [PATCH 44/61] revision (diagram generation) --- .../fta/analysis/fta-cutSet-calculator.ts | 4 +- .../fta/diagram/fta-diagram-generator.ts | 205 ++++++++---------- .../fta/diagram/fta-interfaces.ts | 1 + .../src-language-server/fta/diagram/utils.ts | 45 +--- extension/src-webview/fta-model.ts | 1 + extension/src-webview/fta-views.tsx | 4 +- .../src-webview/options/cut-set-registry.ts | 2 +- extension/webpack.config.js | 2 +- 8 files changed, 107 insertions(+), 157 deletions(-) diff --git a/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts b/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts index a2926b7..1581667 100644 --- a/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts +++ b/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts @@ -40,7 +40,7 @@ export function determineMinimalCutSet(allNodes: AstNode[]): Set checkIfMinimalCutSet(cutSet, allCutSets)); + const minimalCutSet = allCutSets.filter((cutSet) => checkMinimalCutSet(cutSet, allCutSets)); return minimalCutSet; } @@ -51,7 +51,7 @@ export function determineMinimalCutSet(allNodes: AstNode[]): Set, allCutSets: Set[]): boolean { +function checkMinimalCutSet(cutSet: Set, allCutSets: Set[]): boolean { for (const otherCutSet of allCutSets) { let contained = true; otherCutSet.forEach((element) => { diff --git a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts index 5b8b2e3..6bb2536 100644 --- a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts @@ -16,25 +16,17 @@ */ import { AstNode } from "langium"; -import { GeneratorContext, LangiumDiagramGenerator } from "langium-sprotty"; +import { GeneratorContext, IdCache, LangiumDiagramGenerator } from "langium-sprotty"; import { SLabel, SModelElement, SModelRoot } from "sprotty-protocol"; -import { - Component, - Condition, - Gate, - ModelFTA, - TopEvent, - isComponent, - isCondition, - isKNGate -} from "../../generated/ast"; +import { ModelFTA, isComponent, isCondition, isKNGate } from "../../generated/ast"; import { FtaServices } from "../fta-module"; +import { namedFtaElement } from "../utils"; import { FTAEdge, FTANode } from "./fta-interfaces"; import { FTA_EDGE_TYPE, FTA_NODE_TYPE } from "./fta-model"; -import { getAllGateTypes, getFTNodeType, getTargets } from "./utils"; +import { getFTNodeType, getTargets } from "./utils"; export class FtaDiagramGenerator extends LangiumDiagramGenerator { - allNodes: AstNode[]; + protected allNodes: AstNode[]; constructor(services: FtaServices) { super(services); } @@ -46,73 +38,55 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { */ protected generateRoot(args: GeneratorContext): SModelRoot { const { document } = args; - const model: ModelFTA = document.parseResult.value; - //set filter for later maybe - - let ftaChildren: SModelElement[] = model.components?.map((comps) => this.generateFTANode(comps, args)); - - //returns a Map with the gate types as the key and all instances of that type as the value. - const allGates: Map = getAllGateTypes(model.gates); - - //first create the ftaNode for the topevent, conditions and all gates - ftaChildren = ftaChildren.concat([ - this.generateFTANode(model.topEvent, args), - ...model.conditions?.map((cond) => this.generateFTANode(cond, args)).flat(1), - ]); - - allGates.forEach((value: AstNode[]) => { - ftaChildren = ftaChildren.concat([ - ...value?.map((gates) => this.generateFTANode(gates as Gate, args)).flat(1), - ]); - }); - - //after that create the edges of the gates and the top event - allGates.forEach((value: AstNode[]) => { - ftaChildren = ftaChildren.concat([ - ...value?.map((gates) => this.generateEdgesForFTANode(gates, args)).flat(1), - ]); - }); + const model = document.parseResult.value; + const idCache = args.idCache; - ftaChildren = ftaChildren.concat([...this.generateEdgesForFTANode(model.topEvent, args)]); + const ftaChildren: SModelElement[] = [ + // create nodes for top event, components, conditions, and gates + this.generateFTNode(model.topEvent, idCache), + ...model.components.map((component) => this.generateFTNode(component, idCache)), + ...model.conditions.map((condition) => this.generateFTNode(condition, idCache)), + ...model.gates.map((gate) => this.generateFTNode(gate, idCache)), + // create edges for the gates and the top event + ...model.gates.map((gate) => this.generateEdges(gate, idCache)).flat(1), + ...this.generateEdges(model.topEvent, idCache), + ]; - this.allNodes = model.components; - this.allNodes = this.allNodes.concat(model.topEvent, ...model.conditions); - allGates.forEach((value: AstNode[]) => { - this.allNodes = this.allNodes.concat(...value); - }); + // TODO: needed? -> look for a better way to access them for the cut set calculator + // save all nodes + this.allNodes = [model.topEvent, ...model.components, ...model.conditions, ...model.gates]; return { type: "graph", id: "root", - children: ftaChildren + children: ftaChildren, }; } /** - * Getter method for every FTANode in the Fault Tree. - * @returns every FTANode in the Fault Tree. + * Getterfor all nodes of the fault tree. + * @returns all nodes in the fault tree. */ public getNodes(): AstNode[] { return this.allNodes; } /** - * Generates the edges for {@code node}. + * Generates the edges for the given {@code node}. * @param node FTA component for which the edges should be created. - * @param args GeneratorContext of the FTA model. - * @returns edges representing the references {@code node} contains. + * @param idCache The ID cache of the FTA model. + * @returns edges representing the references the given {@code node} contains. */ - private generateEdgesForFTANode(node: AstNode, args: GeneratorContext): SModelElement[] { - const idCache = args.idCache; + private generateEdges(node: AstNode, idCache: IdCache): SModelElement[] { const elements: SModelElement[] = []; const sourceId = idCache.getId(node); // for every reference an edge is created const targets = getTargets(node); for (const target of targets) { const targetId = idCache.getId(target); - const edgeId = idCache.uniqueId(`${sourceId}:-:${targetId}`, undefined); + const edgeId = idCache.uniqueId(`${sourceId}_${targetId}`, undefined); if (sourceId && targetId) { - const e = this.generateFTAEdge(edgeId, sourceId, targetId, "", args); + const e = this.generateFTEdge(edgeId, sourceId, targetId, idCache); elements.push(e); } } @@ -124,27 +98,18 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { * @param edgeId The ID of the edge that should be created. * @param sourceId The ID of the source of the edge. * @param targetId The ID of the target of the edge. + * @param idCache The ID cache of the FTA model. * @param label The label of the edge. - * @param param4 GeneratorContext of the FTA model. * @returns an FTAEdge. */ - private generateFTAEdge( + private generateFTEdge( edgeId: string, sourceId: string, targetId: string, - label: string, - { idCache }: GeneratorContext + idCache: IdCache, + label?: string ): FTAEdge { - let children: SModelElement[] = []; - if (label !== "") { - children = [ - { - type: "label:xref", - id: idCache.uniqueId(edgeId + ".label"), - text: label, - }, - ]; - } + const children = label ? this.createEdgeLabel(label, edgeId, idCache): []; return { type: FTA_EDGE_TYPE, id: edgeId, @@ -158,61 +123,69 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { /** * Generates a single FTANode for the given {@code node}. * @param node The FTA component the node should be created for. - * @param args GeneratorContext of the FTA model. + * @param idCache The ID cache of the FTA model. * @returns a FTANode representing {@code node}. */ - private generateFTANode(node: TopEvent | Gate | Component | Condition, args: GeneratorContext): FTANode { - const idCache = args.idCache; - + private generateFTNode(node: namedFtaElement, idCache: IdCache): FTANode { const nodeId = idCache.uniqueId(node.name, node); + const children: SModelElement[] = this.createNodeLabel(node.name, nodeId, idCache); + const description = isComponent(node) || isCondition(node) ? node.description : ""; + + const ftNode = { + type: FTA_NODE_TYPE, + id: nodeId, + name: node.name, + nodeType: getFTNodeType(node), + description: description, + children: children, + highlight: true, + layout: "stack", + layoutOptions: { + paddingTop: 10.0, + paddingBottom: 10.0, + paddngLeft: 10.0, + paddingRight: 10.0, + }, + } as FTANode; + + if (isKNGate(node)) { + ftNode.k = node.k; + ftNode.n = node.children.length; + } + return ftNode; + } - const children: SModelElement[] = [ + /** + * Generates SLabel element for the given {@code label} of a node. + * @param label Label to translate to SLabel element. + * @param id The ID of the element for which the label should be generated. + * @param idCache The ID cache of the FTA model. + * @returns SLabel element representing {@code label}. + */ + protected createNodeLabel(label: string, id: string, idCache: IdCache): SLabel[] { + return [ { type: "label", - id: idCache.uniqueId(nodeId + ".label"), - text: node.name, + id: idCache.uniqueId(id + ".label"), + text: label, }, ]; + } - let desc = ""; - if (isComponent(node) || isCondition(node)) { - desc = node.description; - } - - if (isKNGate(node)) { - return { - type: FTA_NODE_TYPE, - id: nodeId, - nodeType: getFTNodeType(node), - description: desc, - children: children, - highlight: true, - k: node.k, - n: node.children.length, - layout: "stack", - layoutOptions: { - paddingTop: 10.0, - paddingBottom: 10.0, - paddngLeft: 10.0, - paddingRight: 10.0, - }, - }; - } else { - return { - type: FTA_NODE_TYPE, - id: nodeId, - nodeType: getFTNodeType(node), - description: desc, - children: children, - highlight: true, - layout: "stack", - layoutOptions: { - paddingTop: 10.0, - paddingBottom: 10.0, - paddngLeft: 10.0, - paddingRight: 10.0, - }, - }; - } + /** + * Generates SLabel element for the given {@code label} of an edge. + * @param label Label to translate to SLabel element. + * @param id The ID of the element for which the label should be generated. + * @param idCache The ID cache of the FTA model. + * @returns SLabel element representing {@code label}. + */ + protected createEdgeLabel(label: string, id: string, idCache: IdCache): SLabel[] { + return [ + { + type: "label:xref", + id: idCache.uniqueId(id + ".label"), + text: label, + }, + ]; } } diff --git a/extension/src-language-server/fta/diagram/fta-interfaces.ts b/extension/src-language-server/fta/diagram/fta-interfaces.ts index eaaa9d6..39492e5 100644 --- a/extension/src-language-server/fta/diagram/fta-interfaces.ts +++ b/extension/src-language-server/fta/diagram/fta-interfaces.ts @@ -22,6 +22,7 @@ import { FTNodeType } from "./fta-model"; * Node of a fault tree. */ export interface FTANode extends SNode { + name: string; nodeType: FTNodeType; description: string; highlight?: boolean; diff --git a/extension/src-language-server/fta/diagram/utils.ts b/extension/src-language-server/fta/diagram/utils.ts index 857db59..21976c5 100644 --- a/extension/src-language-server/fta/diagram/utils.ts +++ b/extension/src-language-server/fta/diagram/utils.ts @@ -16,7 +16,16 @@ */ import { AstNode } from "langium"; -import { isTopEvent, isComponent, isCondition, isGate, isAND, isOR, isKNGate, isInhibitGate, Gate } from "../../generated/ast"; +import { + isAND, + isComponent, + isCondition, + isGate, + isInhibitGate, + isKNGate, + isOR, + isTopEvent, +} from "../../generated/ast"; import { FTNodeType } from "./fta-model"; /** @@ -43,7 +52,6 @@ export function getFTNodeType(node: AstNode): FTNodeType { return FTNodeType.UNDEFINED; } - /** * Getter for the references contained in {@code node}. * @param node The AstNode we want the children of. @@ -73,36 +81,3 @@ export function getTargets(node: AstNode): AstNode[] { } return targets; } - - - -/** Sorts every gate with its type and puts them into a two dimensional array - * @param gates Every gate within the FTAModel - * @returns A two dimensional array with every gate sorted into the respective category of And, Or, KN, Inhibit-Gate - */ -export function getAllGateTypes(gates: Gate[]): Map { - const allGates: Map = new Map(); - - const andGates: AstNode[] = []; - const orGates: AstNode[] = []; - const kNGates: AstNode[] = []; - const inhibGates: AstNode[] = []; - - for (const gate of gates) { - if (isAND(gate)) { - andGates.push(gate); - } else if (isOR(gate)) { - orGates.push(gate); - } else if (isKNGate(gate)) { - kNGates.push(gate); - } else if (isInhibitGate(gate)) { - inhibGates.push(gate); - } - } - - allGates.set("AND", andGates); - allGates.set("OR", orGates); - allGates.set("KNGate", kNGates); - allGates.set("InhibitGate", inhibGates); - return allGates; -} \ No newline at end of file diff --git a/extension/src-webview/fta-model.ts b/extension/src-webview/fta-model.ts index 631e01b..2746ea2 100644 --- a/extension/src-webview/fta-model.ts +++ b/extension/src-webview/fta-model.ts @@ -43,6 +43,7 @@ export class FTANode extends SNode { popupFeature, ]; + name: string; nodeType: FTNodeType = FTNodeType.UNDEFINED; description: string = ""; highlight?: boolean; diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index 64e2b43..5e831c0 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -97,7 +97,7 @@ export class FTANodeView extends RectangularNodeView { node.highlight = false; if (node.nodeType === FTNodeType.COMPONENT || node.nodeType === FTNodeType.CONDITION) { //node is component or condition and in the selected cut set. - if (set.includes(node.id)) { + if (set.includes(node.name)) { node.highlight = true; onlyInCutSet = true; @@ -140,7 +140,7 @@ export class FTANodeView extends RectangularNodeView { for (const edge of node.outgoingEdges) { let target = (edge.target as FTANode); if ((target.nodeType === FTNodeType.COMPONENT || target.nodeType === FTNodeType.CONDITION)) { - if (set.includes(target.id)) { + if (set.includes(target.name)) { return true; } } else { diff --git a/extension/src-webview/options/cut-set-registry.ts b/extension/src-webview/options/cut-set-registry.ts index 94d7d42..74a3793 100644 --- a/extension/src-webview/options/cut-set-registry.ts +++ b/extension/src-webview/options/cut-set-registry.ts @@ -69,7 +69,7 @@ export class CutSetsRegistry extends Registry { if (selected === "-") { return "-"; } else { - return selected.split(","); + return selected.split(", "); } } } diff --git a/extension/webpack.config.js b/extension/webpack.config.js index 652892b..1e9f82a 100644 --- a/extension/webpack.config.js +++ b/extension/webpack.config.js @@ -58,7 +58,7 @@ const lsConfig = { const commonWebConfig = { target: 'web', mode: "none", // Leave source code as close as possible. Only set to production during distribution. - devtool: 'nosources-source-map', + devtool: 'eval-source-map', resolve: { extensions: ['.ts', '.tsx', '.js'] }, From e0bcf216535c02cc96e67b13ccc7937054a2435d Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Thu, 21 Sep 2023 17:29:21 +0200 Subject: [PATCH 45/61] revision (WIP) --- .../fta/diagram/fta-layout-config.ts | 9 +---- .../src-language-server/fta/diagram/utils.ts | 38 +++++++++---------- 2 files changed, 18 insertions(+), 29 deletions(-) diff --git a/extension/src-language-server/fta/diagram/fta-layout-config.ts b/extension/src-language-server/fta/diagram/fta-layout-config.ts index 83ce3fc..471adc0 100644 --- a/extension/src-language-server/fta/diagram/fta-layout-config.ts +++ b/extension/src-language-server/fta/diagram/fta-layout-config.ts @@ -20,23 +20,16 @@ import { DefaultLayoutConfigurator } from "sprotty-elk/lib/elk-layout"; import { SGraph, SModelIndex, SNode } from "sprotty-protocol"; export class FtaLayoutConfigurator extends DefaultLayoutConfigurator { - protected graphOptions(sgraph: SGraph, index: SModelIndex): LayoutOptions { - //options for the entire graph. return { - "org.eclipse.elk.spacing.nodeNode": "30.0", "org.eclipse.elk.direction": "DOWN", + "org.eclipse.elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", }; } protected nodeOptions(snode: SNode, index: SModelIndex): LayoutOptions | undefined { - //options for the nodes. return { "org.eclipse.elk.nodeLabels.placement": "INSIDE V_CENTER H_CENTER", - - //'org.eclipse.elk.nodeSize.constraints': 'NODE_LABELS', - "org.eclipse.elk.direction": "DOWN", - "org.eclipse.elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", }; } } diff --git a/extension/src-language-server/fta/diagram/utils.ts b/extension/src-language-server/fta/diagram/utils.ts index 21976c5..7cd5145 100644 --- a/extension/src-language-server/fta/diagram/utils.ts +++ b/extension/src-language-server/fta/diagram/utils.ts @@ -29,9 +29,9 @@ import { import { FTNodeType } from "./fta-model"; /** - * Getter for the type of a FTA component. - * @param node AstNode which type should be determined. - * @returns the type of {@code node}. + * Determines the type of the given {@code node}. + * @param node AstNode, which type should be determined. + * @returns the type of the given {@code node}. */ export function getFTNodeType(node: AstNode): FTNodeType { if (isTopEvent(node)) { @@ -53,29 +53,25 @@ export function getFTNodeType(node: AstNode): FTNodeType { } /** - * Getter for the references contained in {@code node}. - * @param node The AstNode we want the children of. - * @returns The objects {@code node} is traceable to. + * Collects the references contained in {@code node}. + * @param node The AstNode we want the the references from. + * @returns The elements the given {@code node} is traceable to. */ export function getTargets(node: AstNode): AstNode[] { const targets: AstNode[] = []; - if (isTopEvent(node)) { - for (const ref of node.children) { - if (ref?.ref) { - targets.push(ref.ref); - } - } - } else if (isGate(node)) { - for (const ref of node.children) { - if (ref?.ref) { - targets.push(ref.ref); + // only the top event and gates have children + if (isTopEvent(node) || isGate(node)) { + for (const child of node.children) { + if (child.ref) { + targets.push(child.ref); } } - if (isInhibitGate(node)) { - for (const ref of node.condition) { - if (ref?.ref) { - targets.push(ref.ref); - } + } + // inhibit gate has an additional child + if (isInhibitGate(node)) { + for (const child of node.condition) { + if (child.ref) { + targets.push(child.ref); } } } From d010e377e193e8ac5b2d0d1957aaf51444c29484 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Fri, 22 Sep 2023 12:06:54 +0200 Subject: [PATCH 46/61] revision (language-server) --- .../fta/diagram/fta-diagram-generator.ts | 13 ------- .../fta/fta-message-handler.ts | 37 +++++++++---------- .../src-language-server/fta/fta-validator.ts | 33 +++++------------ extension/src-language-server/fta/utils.ts | 2 +- extension/src-language-server/handler.ts | 4 +- extension/src-language-server/main.ts | 24 ++++++------ .../stpa/contextTable/context-dataProvider.ts | 4 +- .../stpa/modelChecking/model-checking.ts | 6 +-- .../stpa/result-report/result-generator.ts | 4 +- extension/src-language-server/stpa/utils.ts | 4 +- extension/src-language-server/utils.ts | 18 ++++++++- extension/src/extension.ts | 8 ++-- 12 files changed, 72 insertions(+), 85 deletions(-) diff --git a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts index 6bb2536..c3b0d7f 100644 --- a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts @@ -26,7 +26,6 @@ import { FTA_EDGE_TYPE, FTA_NODE_TYPE } from "./fta-model"; import { getFTNodeType, getTargets } from "./utils"; export class FtaDiagramGenerator extends LangiumDiagramGenerator { - protected allNodes: AstNode[]; constructor(services: FtaServices) { super(services); } @@ -52,10 +51,6 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { ...this.generateEdges(model.topEvent, idCache), ]; - // TODO: needed? -> look for a better way to access them for the cut set calculator - // save all nodes - this.allNodes = [model.topEvent, ...model.components, ...model.conditions, ...model.gates]; - return { type: "graph", id: "root", @@ -63,14 +58,6 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { }; } - /** - * Getterfor all nodes of the fault tree. - * @returns all nodes in the fault tree. - */ - public getNodes(): AstNode[] { - return this.allNodes; - } - /** * Generates the edges for the given {@code node}. * @param node FTA component for which the edges should be created. diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index 6d360f7..f121fc5 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -15,19 +15,24 @@ * SPDX-License-Identifier: EPL-2.0 */ +import { LangiumSprottySharedServices } from "langium-sprotty"; import { Connection } from "vscode-languageserver"; -import { FtaDiagramGenerator } from "./diagram/fta-diagram-generator"; +import { getFTAModel } from "../utils"; +import { determineMinimalCutSet, generateCutSetsForFT } from "./analysis/fta-cutSet-calculator"; import { FtaServices } from "./fta-module"; import { cutSetsToString } from "./utils"; -import { determineMinimalCutSet, generateCutSetsForFT } from "./analysis/fta-cutSet-calculator"; /** * Adds handlers for notifications regarding fta. * @param connection * @param ftaServices */ -export function addFTANotificationHandler(connection: Connection, ftaServices: FtaServices): void { - addCutSetsHandler(connection, ftaServices); +export function addFTANotificationHandler( + connection: Connection, + ftaServices: FtaServices, + sharedServices: LangiumSprottySharedServices +): void { + addCutSetsHandler(connection, sharedServices); } /** @@ -35,24 +40,18 @@ export function addFTANotificationHandler(connection: Connection, ftaServices: F * @param connection * @param ftaServices */ -function addCutSetsHandler(connection: Connection, ftaServices: FtaServices): void { - connection.onRequest("generate/getCutSets", () => { - const diagramGenerator = ftaServices.diagram.DiagramGenerator as FtaDiagramGenerator; - const nodes = diagramGenerator.getNodes(); - +function addCutSetsHandler(connection: Connection, sharedServices: LangiumSprottySharedServices): void { + connection.onRequest("generate/getCutSets", async (uri: string) => { + const model = await getFTAModel(uri, sharedServices); + const nodes = [model.topEvent, ...model.components, ...model.conditions, ...model.gates]; const cutSets = generateCutSetsForFT(nodes); - const cutSetText = cutSetsToString(cutSets); - - return cutSetText; + return cutSetsToString(cutSets); }); - connection.onRequest("generate/getMinimalCutSets", () => { - const diagramGenerator = ftaServices.diagram.DiagramGenerator as FtaDiagramGenerator; - const nodes = diagramGenerator.getNodes(); - + connection.onRequest("generate/getMinimalCutSets", async (uri: string) => { + const model = await getFTAModel(uri, sharedServices); + const nodes = [model.topEvent, ...model.components, ...model.conditions, ...model.gates]; const minimalCutSets = determineMinimalCutSet(nodes); - const minCutSetToString = cutSetsToString(minimalCutSets, true); - - return minCutSetToString; + return cutSetsToString(minimalCutSets, true); }); } diff --git a/extension/src-language-server/fta/fta-validator.ts b/extension/src-language-server/fta/fta-validator.ts index 8ae0a55..7bef7c3 100644 --- a/extension/src-language-server/fta/fta-validator.ts +++ b/extension/src-language-server/fta/fta-validator.ts @@ -20,7 +20,7 @@ import { ModelFTA, PastaAstType } from "../generated/ast"; import type { FtaServices } from "./fta-module"; /** - * Registry for validation checks. + * Registry for FTA validation checks. */ export class FtaValidationRegistry extends ValidationRegistry { constructor(services: FtaServices) { @@ -43,35 +43,22 @@ export class FtaValidator { * @param accept */ checkModel(model: ModelFTA, accept: ValidationAcceptor): void { - this.checkUniqueIdentifiers(model, accept); + this.checkIDsAreUnique(model, accept); } /** - * Prevent multiple components, conditions and gates from having the same identifier. + * Controls whether the ids of the elements in {@code model} are unique. * @param model The model to validate. * @param accept */ - checkUniqueIdentifiers(model: ModelFTA, accept: ValidationAcceptor): void { + checkIDsAreUnique(model: ModelFTA, accept: ValidationAcceptor): void { const componentNames = new Set(); - model.components.forEach((c) => { - if (componentNames.has(c.name)) { - accept("error", `Component has non-unique name '${c.name}'.`, { node: c, property: "name" }); + const namedElements = [...model.components, ...model.conditions, ...model.gates]; + for (const element of namedElements) { + if (componentNames.has(element.name)) { + accept("error", `All identifiers must be unique.`, { node: element, property: "name" }); } - componentNames.add(c.name); - }); - model.conditions.forEach((c) => { - if (componentNames.has(c.name)) { - accept("error", `Condition has non-unique name '${c.name}'.`, { node: c, property: "name" }); - } - componentNames.add(c.name); - }); - - const gateNames = new Set(); - model.gates.forEach((g) => { - if (gateNames.has(g.name) || componentNames.has(g.name)) { - accept("error", `Gate has non-unique name '${g.name}'.`, { node: g, property: "name" }); - } - gateNames.add(g.name); - }); + componentNames.add(element.name); + } } } diff --git a/extension/src-language-server/fta/utils.ts b/extension/src-language-server/fta/utils.ts index e9633cb..cd4e023 100644 --- a/extension/src-language-server/fta/utils.ts +++ b/extension/src-language-server/fta/utils.ts @@ -33,4 +33,4 @@ export function cutSetsToString(cutSets: Set[], minimal?: boole text += ` cut sets are:\n`; text += `[${cutSets.map((cutSet) => `[${[...cutSet].map((element) => element.name).join(", ")}]`).join(",\n")}]`; return text; -} +} \ No newline at end of file diff --git a/extension/src-language-server/handler.ts b/extension/src-language-server/handler.ts index 014e3f3..ddc02ce 100644 --- a/extension/src-language-server/handler.ts +++ b/extension/src-language-server/handler.ts @@ -18,7 +18,7 @@ import { LangiumSprottySharedServices } from "langium-sprotty"; import { Model } from "./generated/ast"; import { Connection, Range } from "vscode-languageserver"; -import { getModel } from "./utils"; +import { getSTPAModel } from "./utils"; import { elementWithName } from "./stpa/utils"; /** @@ -30,7 +30,7 @@ export function addNotificationHandler(connection: Connection, shared: LangiumSp // diagram connection.onNotification("diagram/selected", async (msg: { label: string; uri: string }) => { // get the current model - const model = await getModel(msg.uri, shared); + const model = await getSTPAModel(msg.uri, shared); // determine the range in the editor of the component identified by "label" const range = getRangeOfNode(model, msg.label); diff --git a/extension/src-language-server/main.ts b/extension/src-language-server/main.ts index 21bef5c..754cae5 100644 --- a/extension/src-language-server/main.ts +++ b/extension/src-language-server/main.ts @@ -15,31 +15,31 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { startLanguageServer } from 'langium'; -import { NodeFileSystem } from 'langium/node'; -import { addDiagramHandler } from 'langium-sprotty'; -import { createConnection, ProposedFeatures } from 'vscode-languageserver/node'; -import { addSTPANotificationHandler } from './stpa/message-handler'; -import { addNotificationHandler } from './handler'; -import { createServices } from './module'; -import { addFTANotificationHandler } from './fta/fta-message-handler'; +import { startLanguageServer } from "langium"; +import { addDiagramHandler } from "langium-sprotty"; +import { NodeFileSystem } from "langium/node"; +import { createConnection, ProposedFeatures } from "vscode-languageserver/node"; +import { addFTANotificationHandler } from "./fta/fta-message-handler"; +import { addNotificationHandler } from "./handler"; +import { createServices } from "./module"; +import { addSTPANotificationHandler } from "./stpa/message-handler"; // Create a connection to the client const connection = createConnection(ProposedFeatures.all); // Inject the language services -const { shared, stpa, fta} = createServices({ connection, ...NodeFileSystem }); +const { shared, stpa, fta } = createServices({ connection, ...NodeFileSystem }); // Start the language server with the language-specific services startLanguageServer(shared); addDiagramHandler(connection, shared); addSTPANotificationHandler(connection, stpa, shared); -addFTANotificationHandler(connection, fta); +addFTANotificationHandler(connection, fta, shared); addNotificationHandler(connection, shared); // handle configuration changes for the validation checks -connection.onNotification('configuration', options => { +connection.onNotification("configuration", (options) => { for (const option of options) { switch (option.id) { case "checkResponsibilitiesForConstraints": @@ -58,4 +58,4 @@ connection.onNotification('configuration', options => { } }); -connection.onInitialized(() => connection.sendNotification('ready')); \ No newline at end of file +connection.onInitialized(() => connection.sendNotification("ready")); diff --git a/extension/src-language-server/stpa/contextTable/context-dataProvider.ts b/extension/src-language-server/stpa/contextTable/context-dataProvider.ts index 0fb9dcb..cf60016 100644 --- a/extension/src-language-server/stpa/contextTable/context-dataProvider.ts +++ b/extension/src-language-server/stpa/contextTable/context-dataProvider.ts @@ -26,7 +26,7 @@ import { ContextTableVariableValues, } from "../../../src-context-table/utils"; import { Model } from "../../generated/ast"; -import { getModel } from "../../utils"; +import { getSTPAModel } from "../../utils"; import { StpaServices } from "../stpa-module"; export class ContextTableProvider { @@ -66,7 +66,7 @@ export class ContextTableProvider { */ async getData(uri: URI): Promise { // get the current model - const model = await getModel(uri, this.services.shared); + const model = await getSTPAModel(uri, this.services.shared); const actions: ContextTableControlAction[] = []; const variables: ContextTableSystemVariables[] = []; diff --git a/extension/src-language-server/stpa/modelChecking/model-checking.ts b/extension/src-language-server/stpa/modelChecking/model-checking.ts index 1b99ab2..e4e6a1c 100644 --- a/extension/src-language-server/stpa/modelChecking/model-checking.ts +++ b/extension/src-language-server/stpa/modelChecking/model-checking.ts @@ -19,7 +19,7 @@ import { Reference } from "langium"; import { LangiumSprottySharedServices } from "langium-sprotty"; import { URI } from "vscode-uri"; import { DCARule, Model, Rule, Variable, VariableValue, isRule } from "../../generated/ast"; -import { getModel } from "../../utils"; +import { getSTPAModel } from "../../utils"; /** * Respresents an LTL formula. @@ -71,14 +71,14 @@ export async function generateLTLFormulae( shared: LangiumSprottySharedServices ): Promise> { // get the current model - let model = await getModel(uri, shared); + let model = await getSTPAModel(uri, shared); // references are not found if the stpa file has not been opened since then the linter has not been activated yet if (model.rules.length > 0 && model.rules[0]?.contexts[0]?.vars[0]?.ref === undefined) { // build document await shared.workspace.DocumentBuilder.update([URI.parse(uri)], []); // update the model - model = await getModel(uri, shared); + model = await getSTPAModel(uri, shared); } // ltl formulas are saved per controller const map: Record = {}; diff --git a/extension/src-language-server/stpa/result-report/result-generator.ts b/extension/src-language-server/stpa/result-report/result-generator.ts index e25d225..b3834f1 100644 --- a/extension/src-language-server/stpa/result-report/result-generator.ts +++ b/extension/src-language-server/stpa/result-report/result-generator.ts @@ -28,7 +28,7 @@ import { SystemConstraint, UCA } from "../../generated/ast"; -import { getModel } from "../../utils"; +import { getSTPAModel } from "../../utils"; import { StpaComponent, StpaResult, UCA_TYPE } from "../utils"; /** @@ -40,7 +40,7 @@ import { StpaComponent, StpaResult, UCA_TYPE } from "../utils"; export async function createResultData(uri: string, shared: LangiumSprottySharedServices): Promise { const result: StpaResult = new StpaResult(); // get the current model - const model = await getModel(uri, shared); + const model = await getSTPAModel(uri, shared); // losses const resultLosses: { id: string; description: string }[] = []; diff --git a/extension/src-language-server/stpa/utils.ts b/extension/src-language-server/stpa/utils.ts index bb1287d..bf0e4d0 100644 --- a/extension/src-language-server/stpa/utils.ts +++ b/extension/src-language-server/stpa/utils.ts @@ -34,7 +34,7 @@ import { UCA, Variable, } from "../generated/ast"; -import { getModel } from "../utils"; +import { getSTPAModel } from "../utils"; export type leafElement = | Loss @@ -81,7 +81,7 @@ export async function getControlActions( ): Promise> { const controlActionsMap: Record = {}; // get the model from the file determined by the uri - const model = await getModel(uri, shared); + const model = await getSTPAModel(uri, shared); // collect control actions grouped by their controller model.controlStructure?.nodes.forEach((systemComponent) => { systemComponent.actions.forEach((action) => { diff --git a/extension/src-language-server/utils.ts b/extension/src-language-server/utils.ts index 7f45191..1d4c64c 100644 --- a/extension/src-language-server/utils.ts +++ b/extension/src-language-server/utils.ts @@ -18,7 +18,9 @@ import { LangiumDocument, LangiumSharedServices } from "langium"; import { LangiumSprottySharedServices } from "langium-sprotty"; import { URI } from 'vscode-uri'; -import { Model } from './generated/ast'; +import { Model, ModelFTA } from './generated/ast'; + +// TODO: merge the functions into one? /** * Determines the model for {@code uri}. @@ -26,7 +28,7 @@ import { Model } from './generated/ast'; * @param shared The shared service. * @returns the model for the given uri. */ -export async function getModel(uri: string, shared: LangiumSprottySharedServices | LangiumSharedServices): Promise { +export async function getSTPAModel(uri: string, shared: LangiumSprottySharedServices | LangiumSharedServices): Promise { const textDocuments = shared.workspace.LangiumDocuments; const currentDoc = textDocuments.getOrCreateDocument(URI.parse(uri)) as LangiumDocument; let currentModel = currentDoc.parseResult.value; @@ -37,4 +39,16 @@ export async function getModel(uri: string, shared: LangiumSprottySharedServices currentModel = currentDoc.parseResult.value; } return currentModel; +} + +/** + * Determines the model for {@code uri}. + * @param uri The URI for which the model is desired. + * @param shared The shared service. + * @returns the model for the given uri. + */ +export async function getFTAModel(uri: string, shared: LangiumSprottySharedServices | LangiumSharedServices): Promise { + const textDocuments = shared.workspace.LangiumDocuments; + const currentDoc = textDocuments.getOrCreateDocument(URI.parse(uri)) as LangiumDocument; + return currentDoc.parseResult.value; } \ No newline at end of file diff --git a/extension/src/extension.ts b/extension/src/extension.ts index b74d092..fc8c64b 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -173,16 +173,16 @@ function registerSTPACommands(manager: StpaLspVscodeExtension, context: vscode.E // commands for computing and displaying the (minimal) cut sets of the fault tree. context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.fta.cutSets', async () =>{ - const cutSets:string = await languageClient.sendRequest('generate/getCutSets'); + vscode.commands.registerCommand(options.extensionPrefix + '.fta.cutSets', async (uri: vscode.Uri) =>{ + const cutSets:string = await languageClient.sendRequest('generate/getCutSets', uri.path); //Send cut sets to webview to display them in a dropdown menu. dispatchCutSetsToWebview(manager, cutSets); createOutputChannel(cutSets, "FTA Cut Sets"); }) ); context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.fta.minimalCutSets', async () =>{ - const minimalCutSets:string = await languageClient.sendRequest('generate/getMinimalCutSets'); + vscode.commands.registerCommand(options.extensionPrefix + '.fta.minimalCutSets', async (uri: vscode.Uri) =>{ + const minimalCutSets:string = await languageClient.sendRequest('generate/getMinimalCutSets', uri.path); dispatchCutSetsToWebview(manager, minimalCutSets); createOutputChannel(minimalCutSets, "FTA Cut Sets"); }) From 6064b42aabb837f8a553ba3a36180204a7251ead Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Fri, 22 Sep 2023 16:07:41 +0200 Subject: [PATCH 47/61] revision webview (WIP) --- .../src-language-server/diagram-server.ts | 3 + extension/src-language-server/handler.ts | 33 +- extension/src-webview/css/diagram.css | 371 +----------------- extension/src-webview/css/fta-diagram.css | 28 +- extension/src-webview/css/fta-theme.css | 29 -- extension/src-webview/css/stpa-diagram.css | 355 +++++++++++++++++ extension/src-webview/css/theme.css | 9 +- extension/src-webview/di.config.ts | 14 +- extension/src-webview/di.symbols.ts | 6 +- extension/src-webview/fta-views.tsx | 125 +++--- extension/src-webview/helper-methods.ts | 2 - extension/src-webview/options/actions.ts | 10 +- .../src-webview/options/cut-set-panel.tsx | 8 +- .../src-webview/options/cut-set-registry.ts | 41 +- .../src-webview/options/options-module.ts | 1 - .../src-webview/options/options-renderer.tsx | 8 +- .../src-webview/{views.tsx => stpa-views.tsx} | 6 +- extension/src/actions.ts | 10 +- extension/src/extension.ts | 11 +- extension/src/language-extension.ts | 5 +- 20 files changed, 518 insertions(+), 557 deletions(-) delete mode 100644 extension/src-webview/css/fta-theme.css create mode 100644 extension/src-webview/css/stpa-diagram.css rename extension/src-webview/{views.tsx => stpa-views.tsx} (97%) diff --git a/extension/src-language-server/diagram-server.ts b/extension/src-language-server/diagram-server.ts index eee61a7..cf69ae8 100644 --- a/extension/src-language-server/diagram-server.ts +++ b/extension/src-language-server/diagram-server.ts @@ -89,6 +89,9 @@ export class PastaDiagramServer extends DiagramServer { return this.handleSetSynthesisOption(action as SetSynthesisOptionsAction); case GenerateSVGsAction.KIND: return this.handleGenerateSVGDiagrams(action as GenerateSVGsAction); + // TODO: implement selection of an element here instead of in "handler" and "wview" ?? + // case SelectAction.KIND: + // return this.handleSelectaction } return super.handleAction(action); } diff --git a/extension/src-language-server/handler.ts b/extension/src-language-server/handler.ts index ddc02ce..0cb0e4c 100644 --- a/extension/src-language-server/handler.ts +++ b/extension/src-language-server/handler.ts @@ -29,22 +29,25 @@ import { elementWithName } from "./stpa/utils"; export function addNotificationHandler(connection: Connection, shared: LangiumSprottySharedServices): void { // diagram connection.onNotification("diagram/selected", async (msg: { label: string; uri: string }) => { - // get the current model - const model = await getSTPAModel(msg.uri, shared); + // TODO: implement for FTA + if (msg.uri.endsWith(".stpa")) { + // get the current model + const model = await getSTPAModel(msg.uri, shared); - // determine the range in the editor of the component identified by "label" - const range = getRangeOfNode(model, msg.label); - if (range) { - // notify extension to highlight the range in the editor - connection.sendNotification("editor/highlight", { - startLine: range.start.line, - startChar: range.start.character, - endLine: range.end.line, - endChar: range.end.character, - uri: msg.uri, - }); - } else { - console.log("The selected UCA could not be found in the editor."); + // determine the range in the editor of the component identified by "label" + const range = getRangeOfNode(model, msg.label); + if (range) { + // notify extension to highlight the range in the editor + connection.sendNotification("editor/highlight", { + startLine: range.start.line, + startChar: range.start.character, + endLine: range.end.line, + endChar: range.end.character, + uri: msg.uri, + }); + } else { + console.log("The selected element could not be found in the editor."); + } } }); } diff --git a/extension/src-webview/css/diagram.css b/extension/src-webview/css/diagram.css index 347ead3..ed2f200 100644 --- a/extension/src-webview/css/diagram.css +++ b/extension/src-webview/css/diagram.css @@ -1,90 +1,10 @@ @import "./options.css"; @import "./sidebar.css"; @import "./theme.css"; +@import "./stpa-diagram.css"; @import "./fta-diagram.css"; -.vscode-high-contrast .stpa-node[aspect="0"], -.vscode-high-contrast .stpa-edge-arrow[aspect="0"] { - fill: #AD0000; -} - -.vscode-high-contrast .stpa-node[aspect="1"], -.vscode-high-contrast .stpa-edge-arrow[aspect="1"] { - fill: #A85400; -} - -.vscode-high-contrast .stpa-node[aspect="2"], -.vscode-high-contrast .stpa-edge-arrow[aspect="2"] { - fill: #006600; -} - -.vscode-high-contrast .stpa-node[aspect="3"], -.vscode-high-contrast .stpa-edge-arrow[aspect="3"] { - fill: purple; -} - -.vscode-high-contrast .stpa-node[aspect="4"], -.vscode-high-contrast .stpa-edge-arrow[aspect="4"] { - fill: #0054A8; -} - -.vscode-high-contrast .stpa-node[aspect="5"], -.vscode-high-contrast .stpa-edge-arrow[aspect="5"] { - fill: #006161; -} - -.vscode-high-contrast .stpa-node[aspect="6"], -.vscode-high-contrast .stpa-edge-arrow[aspect="6"] { - fill: #616100; -} - -.vscode-high-contrast .stpa-node[aspect="7"], -.vscode-high-contrast .stpa-edge-arrow[aspect="7"] { - fill: #2f4f4f; -} - - -.vscode-high-contrast .stpa-edge[aspect="0"], -.vscode-high-contrast .stpa-edge-arrow[aspect="0"] { - stroke: #AD0000; -} - -.vscode-high-contrast .stpa-edge[aspect="1"], -.vscode-high-contrast .stpa-edge-arrow[aspect="1"] { - stroke: #A85400; -} - -.vscode-high-contrast .stpa-edge[aspect="2"], -.vscode-high-contrast .stpa-edge-arrow[aspect="2"] { - stroke: #006600; -} - -.vscode-high-contrast .stpa-edge[aspect="3"], -.vscode-high-contrast .stpa-edge-arrow[aspect="3"] { - stroke: purple; -} - -.vscode-high-contrast .stpa-edge[aspect="4"], -.vscode-high-contrast .stpa-edge-arrow[aspect="4"] { - stroke: #0054A8; -} - -.vscode-high-contrast .stpa-edge[aspect="5"], -.vscode-high-contrast .stpa-edge-arrow[aspect="5"] { - stroke: #006161; -} - -.vscode-high-contrast .stpa-edge[aspect="6"], -.vscode-high-contrast .stpa-edge-arrow[aspect="6"] { - stroke: #616100; -} - -.vscode-high-contrast .stpa-edge[aspect="7"], -.vscode-high-contrast .stpa-edge-arrow[aspect="7"] { - stroke: #2f4f4f; -} - - +/* sprotty and black/white colors */ .vscode-high-contrast .print-node { fill: black; stroke: white; @@ -95,134 +15,8 @@ stroke: black; } - - - -.vscode-dark .stpa-node[aspect="0"] { - fill: var(--stpa-dark-node); - stroke: var(--stpa-loss-dark); - stroke-width: 2; -} - -.vscode-dark .stpa-node[aspect="1"] { - fill: var(--stpa-dark-node); - stroke: var(--stpa-hazard-dark); - stroke-width: 2; -} - -.vscode-dark .stpa-node[aspect="2"] { - fill: var(--stpa-dark-node); - stroke: var(--stpa-sys-constraint-dark); - stroke-width: 2; -} - -.vscode-dark .stpa-node[aspect="3"] { - fill: var(--stpa-dark-node); - stroke: var(--stpa-responsibility-dark); - stroke-width: 2; -} - -.vscode-dark .stpa-node[aspect="4"] { - fill: var(--stpa-dark-node); - stroke: var(--stpa-uca-dark); - stroke-width: 2; -} - -.vscode-dark .stpa-node[aspect="5"] { - fill: var(--stpa-dark-node); - stroke: var(--stpa-cont-constraint-dark); - stroke-width: 2; -} - -.vscode-dark .stpa-node[aspect="6"] { - fill: var(--stpa-dark-node); - stroke: var(--stpa-scenario-dark); - stroke-width: 2; -} - -.vscode-dark .stpa-node[aspect="7"] { - fill: var(--stpa-dark-node); - stroke: var(--stpa-safety-requirement-dark); - stroke-width: 2; -} - - -.vscode-dark .stpa-edge-arrow[aspect="0"] { - fill: var(--stpa-loss-dark); -} - -.vscode-dark .stpa-edge-arrow[aspect="1"] { - fill: var(--stpa-hazard-dark); -} - -.vscode-dark .stpa-edge-arrow[aspect="2"] { - fill: var(--stpa-sys-constraint-dark); -} - -.vscode-dark .stpa-edge-arrow[aspect="3"] { - fill: var(--stpa-responsibility-dark); -} - -.vscode-dark .stpa-edge-arrow[aspect="4"] { - fill: var(--stpa-uca-dark); -} - -.vscode-dark .stpa-edge-arrow[aspect="5"] { - fill: var(--stpa-cont-constraint-dark); -} - -.vscode-dark .stpa-edge-arrow[aspect="6"] { - fill: var(--stpa-scenario-dark); -} - -.vscode-dark .stpa-edge-arrow[aspect="7"] { - fill: var(--stpa-safety-requirement-dark); -} - - -.vscode-dark .stpa-edge[aspect="0"], -.vscode-dark .stpa-edge-arrow[aspect="0"] { - stroke: var(--stpa-loss-dark); -} - -.vscode-dark .stpa-edge[aspect="1"], -.vscode-dark .stpa-edge-arrow[aspect="1"] { - stroke: var(--stpa-hazard-dark); -} - -.vscode-dark .stpa-edge[aspect="2"], -.vscode-dark .stpa-edge-arrow[aspect="2"] { - stroke: var(--stpa-sys-constraint-dark); -} - -.vscode-dark .stpa-edge[aspect="3"], -.vscode-dark .stpa-edge-arrow[aspect="3"] { - stroke: var(--stpa-responsibility-dark); -} - -.vscode-dark .stpa-edge[aspect="4"], -.vscode-dark .stpa-edge-arrow[aspect="4"] { - stroke: var(--stpa-uca-dark); -} - -.vscode-dark .stpa-edge[aspect="5"], -.vscode-dark .stpa-edge-arrow[aspect="5"] { - stroke: var(--stpa-cont-constraint-dark); -} - -.vscode-dark .stpa-edge[aspect="6"], -.vscode-dark .stpa-edge-arrow[aspect="6"] { - stroke: var(--stpa-scenario-dark); -} - -.vscode-dark .stpa-edge[aspect="7"], -.vscode-dark .stpa-edge-arrow[aspect="7"] { - stroke: var(--stpa-safety-requirement-dark); -} - - .vscode-dark .print-node { - fill: var(--stpa-dark-node); + fill: var(--dark-node); stroke: var(--vscode-editor-foreground); } @@ -231,134 +25,8 @@ stroke: black; } - - - -.vscode-light .stpa-node[aspect="0"] { - fill: var(--stpa-light-node); - stroke: var(--stpa-loss-light); - stroke-width: 2; -} - -.vscode-light .stpa-node[aspect="1"] { - fill: var(--stpa-light-node); - stroke: var(--stpa-hazard-light); - stroke-width: 2; -} - -.vscode-light .stpa-node[aspect="2"] { - fill: var(--stpa-light-node); - stroke: var(--stpa-sys-constraint-light); - stroke-width: 2; -} - -.vscode-light .stpa-node[aspect="3"] { - fill: var(--stpa-light-node); - stroke: var(--stpa-responsibility-light); - stroke-width: 2; -} - -.vscode-light .stpa-node[aspect="4"] { - fill: var(--stpa-light-node); - stroke: var(--stpa-uca-light); - stroke-width: 2; -} - -.vscode-light .stpa-node[aspect="5"] { - fill: var(--stpa-light-node); - stroke: var(--stpa-cont-constraint-light); - stroke-width: 2; -} - -.vscode-light .stpa-node[aspect="6"] { - fill: var(--stpa-light-node); - stroke: var(--stpa-scenario-light); - stroke-width: 2; -} - -.vscode-light .stpa-node[aspect="7"] { - fill: var(--stpa-light-node); - stroke: var(--stpa-safety-requirement-light); - stroke-width: 2; -} - - -.vscode-light .stpa-edge-arrow[aspect="0"] { - fill: var(--stpa-loss-light); -} - -.vscode-light .stpa-edge-arrow[aspect="1"] { - fill: var(--stpa-hazard-light); -} - -.vscode-light .stpa-edge-arrow[aspect="2"] { - fill: var(--stpa-sys-constraint-light); -} - -.vscode-light .stpa-edge-arrow[aspect="3"] { - fill: var(--stpa-responsibility-light); -} - -.vscode-light .stpa-edge-arrow[aspect="4"] { - fill: var(--stpa-uca-light); -} - -.vscode-light .stpa-edge-arrow[aspect="5"] { - fill: var(--stpa-cont-constraint-light); -} - -.vscode-light .stpa-edge-arrow[aspect="6"] { - fill: var(--stpa-scenario-light); -} - -.vscode-light .stpa-edge-arrow[aspect="7"] { - fill: var(--stpa-safety-requirement-light); -} - - -.vscode-light .stpa-edge[aspect="0"], -.vscode-light .stpa-edge-arrow[aspect="0"] { - stroke: var(--stpa-loss-light); -} - -.vscode-light .stpa-edge[aspect="1"], -.vscode-light .stpa-edge-arrow[aspect="1"] { - stroke: var(--stpa-hazard-light); -} - -.vscode-light .stpa-edge[aspect="2"], -.vscode-light .stpa-edge-arrow[aspect="2"] { - stroke: var(--stpa-sys-constraint-light); -} - -.vscode-light .stpa-edge[aspect="3"], -.vscode-light .stpa-edge-arrow[aspect="3"] { - stroke: var(--stpa-responsibility-light); -} - -.vscode-light .stpa-edge[aspect="4"], -.vscode-light .stpa-edge-arrow[aspect="4"] { - stroke: var(--stpa-uca-light); -} - -.vscode-light .stpa-edge[aspect="5"], -.vscode-light .stpa-edge-arrow[aspect="5"] { - stroke: var(--stpa-cont-constraint-light); -} - -.vscode-light .stpa-edge[aspect="6"], -.vscode-light .stpa-edge-arrow[aspect="6"] { - stroke: var(--stpa-scenario-light); -} - -.vscode-light .stpa-edge[aspect="7"], -.vscode-light .stpa-edge-arrow[aspect="7"] { - stroke: var(--stpa-safety-requirement-light); -} - - .vscode-light .print-node { - fill: var(--stpa-light-node); + fill: var(--light-node); stroke: black; } @@ -367,11 +35,6 @@ stroke: black; } - -.feedback-edge { - stroke-dasharray: 5, 5; -} - .print-edge { stroke: var(--vscode-editor-foreground); } @@ -381,20 +44,23 @@ stroke: var(--vscode-editor-foreground); } - -.stpa-node { - stroke: black; -} - +/* Selected node */ .sprotty-node.selected { stroke-width: 2; stroke: var(--vscode-button-hover-background); } -.hidden { - opacity: 0.4; +.node-selected { + stroke-width: 5; + stroke: var(--vscode-button-hover-background); +} + +/* greyed out element */ +.greyed-out { + opacity: 0.2; } +/* label */ body[class='vscode-light'] .sprotty-label { fill: black; stroke-width: 0; @@ -410,6 +76,7 @@ body[class='vscode-light'] .sprotty-label { fill: var(--vscode-editorActiveLineNumber-foreground); } +/* Sprotty edges */ body[class='vscode-light'] .sprotty-edge .sprotty-label { fill: black } @@ -464,10 +131,4 @@ body[class='vscode-light'] .sprotty-edge-arrow { .sprotty-hidden { width: 0px; height: 0px; -} - - -.node-selected { - stroke-width: 5; - stroke: var(--vscode-button-hover-background); -} +} \ No newline at end of file diff --git a/extension/src-webview/css/fta-diagram.css b/extension/src-webview/css/fta-diagram.css index 6372b17..68a9d13 100644 --- a/extension/src-webview/css/fta-diagram.css +++ b/extension/src-webview/css/fta-diagram.css @@ -15,30 +15,26 @@ * SPDX-License-Identifier: EPL-2.0 */ -@import "./fta-theme.css"; +@import "./theme.css"; .vscode-dark .fta-node { - fill: var(--fta-dark-node); - stroke: var(--fta-light-node); + fill: var(--dark-node); + stroke: var(--light-node); stroke-width: 2; } - .vscode-dark .fta-edge { - stroke: var(--fta-light-node); + stroke: var(--light-node); } - .vscode-light .fta-node { - fill: var(--fta-light-node); + fill: var(--light-node); stroke: black; stroke-width: 2; } - .vscode-light .fta-edge { - stroke: var(--fta-darker-node); - stroke-width: 2; + stroke: black; } .fta-text{ @@ -47,14 +43,6 @@ font-size: 10px; } -.fta-hidden { - opacity: 0.1; -} - -.vscode-dark .fta-highlight-node{ - stroke: var(--fta-highlight-node); +.fta-highlight-node{ + stroke: var(--highlight-node); } - -.vscode-light .fta-highlight-node{ - stroke: var(--fta-highlight-node); -} \ No newline at end of file diff --git a/extension/src-webview/css/fta-theme.css b/extension/src-webview/css/fta-theme.css deleted file mode 100644 index 45ab116..0000000 --- a/extension/src-webview/css/fta-theme.css +++ /dev/null @@ -1,29 +0,0 @@ -/* - * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient - * - * http://rtsys.informatik.uni-kiel.de/kieler - * - * Copyright 2023 by - * + Kiel University - * + Department of Computer Science - * + Real-Time and Embedded Systems Group - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - */ - - :root { - - - /* Colors for fta nodes */ - - --fta-darker-node: black; - --fta-light-node: white; - --fta-dark-node: #ffffff00; - --fta-highlight-node: #FF2828; - - -} diff --git a/extension/src-webview/css/stpa-diagram.css b/extension/src-webview/css/stpa-diagram.css new file mode 100644 index 0000000..6512724 --- /dev/null +++ b/extension/src-webview/css/stpa-diagram.css @@ -0,0 +1,355 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kielerMontreal, Québec, Kanada + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +@import "./theme.css"; + +.stpa-node { + stroke: black; +} + +/* Feedback edges */ +.feedback-edge { + stroke-dasharray: 5, 5; +} + +/* High contrast theme */ +.vscode-high-contrast .stpa-node[aspect="0"], +.vscode-high-contrast .stpa-edge-arrow[aspect="0"] { + fill: #AD0000; +} + +.vscode-high-contrast .stpa-node[aspect="1"], +.vscode-high-contrast .stpa-edge-arrow[aspect="1"] { + fill: #A85400; +} + +.vscode-high-contrast .stpa-node[aspect="2"], +.vscode-high-contrast .stpa-edge-arrow[aspect="2"] { + fill: #006600; +} + +.vscode-high-contrast .stpa-node[aspect="3"], +.vscode-high-contrast .stpa-edge-arrow[aspect="3"] { + fill: purple; +} + +.vscode-high-contrast .stpa-node[aspect="4"], +.vscode-high-contrast .stpa-edge-arrow[aspect="4"] { + fill: #0054A8; +} + +.vscode-high-contrast .stpa-node[aspect="5"], +.vscode-high-contrast .stpa-edge-arrow[aspect="5"] { + fill: #006161; +} + +.vscode-high-contrast .stpa-node[aspect="6"], +.vscode-high-contrast .stpa-edge-arrow[aspect="6"] { + fill: #616100; +} + +.vscode-high-contrast .stpa-node[aspect="7"], +.vscode-high-contrast .stpa-edge-arrow[aspect="7"] { + fill: #2f4f4f; +} + + +.vscode-high-contrast .stpa-edge[aspect="0"], +.vscode-high-contrast .stpa-edge-arrow[aspect="0"] { + stroke: #AD0000; +} + +.vscode-high-contrast .stpa-edge[aspect="1"], +.vscode-high-contrast .stpa-edge-arrow[aspect="1"] { + stroke: #A85400; +} + +.vscode-high-contrast .stpa-edge[aspect="2"], +.vscode-high-contrast .stpa-edge-arrow[aspect="2"] { + stroke: #006600; +} + +.vscode-high-contrast .stpa-edge[aspect="3"], +.vscode-high-contrast .stpa-edge-arrow[aspect="3"] { + stroke: purple; +} + +.vscode-high-contrast .stpa-edge[aspect="4"], +.vscode-high-contrast .stpa-edge-arrow[aspect="4"] { + stroke: #0054A8; +} + +.vscode-high-contrast .stpa-edge[aspect="5"], +.vscode-high-contrast .stpa-edge-arrow[aspect="5"] { + stroke: #006161; +} + +.vscode-high-contrast .stpa-edge[aspect="6"], +.vscode-high-contrast .stpa-edge-arrow[aspect="6"] { + stroke: #616100; +} + +.vscode-high-contrast .stpa-edge[aspect="7"], +.vscode-high-contrast .stpa-edge-arrow[aspect="7"] { + stroke: #2f4f4f; +} + +/* Dark theme */ +.vscode-dark .stpa-node[aspect="0"] { + fill: var(--dark-node); + stroke: var(--stpa-loss-dark); + stroke-width: 2; +} + +.vscode-dark .stpa-node[aspect="1"] { + fill: var(--dark-node); + stroke: var(--stpa-hazard-dark); + stroke-width: 2; +} + +.vscode-dark .stpa-node[aspect="2"] { + fill: var(--dark-node); + stroke: var(--stpa-sys-constraint-dark); + stroke-width: 2; +} + +.vscode-dark .stpa-node[aspect="3"] { + fill: var(--dark-node); + stroke: var(--stpa-responsibility-dark); + stroke-width: 2; +} + +.vscode-dark .stpa-node[aspect="4"] { + fill: var(--dark-node); + stroke: var(--stpa-uca-dark); + stroke-width: 2; +} + +.vscode-dark .stpa-node[aspect="5"] { + fill: var(--dark-node); + stroke: var(--stpa-cont-constraint-dark); + stroke-width: 2; +} + +.vscode-dark .stpa-node[aspect="6"] { + fill: var(--dark-node); + stroke: var(--stpa-scenario-dark); + stroke-width: 2; +} + +.vscode-dark .stpa-node[aspect="7"] { + fill: var(--dark-node); + stroke: var(--stpa-safety-requirement-dark); + stroke-width: 2; +} + + +.vscode-dark .stpa-edge-arrow[aspect="0"] { + fill: var(--stpa-loss-dark); +} + +.vscode-dark .stpa-edge-arrow[aspect="1"] { + fill: var(--stpa-hazard-dark); +} + +.vscode-dark .stpa-edge-arrow[aspect="2"] { + fill: var(--stpa-sys-constraint-dark); +} + +.vscode-dark .stpa-edge-arrow[aspect="3"] { + fill: var(--stpa-responsibility-dark); +} + +.vscode-dark .stpa-edge-arrow[aspect="4"] { + fill: var(--stpa-uca-dark); +} + +.vscode-dark .stpa-edge-arrow[aspect="5"] { + fill: var(--stpa-cont-constraint-dark); +} + +.vscode-dark .stpa-edge-arrow[aspect="6"] { + fill: var(--stpa-scenario-dark); +} + +.vscode-dark .stpa-edge-arrow[aspect="7"] { + fill: var(--stpa-safety-requirement-dark); +} + + +.vscode-dark .stpa-edge[aspect="0"], +.vscode-dark .stpa-edge-arrow[aspect="0"] { + stroke: var(--stpa-loss-dark); +} + +.vscode-dark .stpa-edge[aspect="1"], +.vscode-dark .stpa-edge-arrow[aspect="1"] { + stroke: var(--stpa-hazard-dark); +} + +.vscode-dark .stpa-edge[aspect="2"], +.vscode-dark .stpa-edge-arrow[aspect="2"] { + stroke: var(--stpa-sys-constraint-dark); +} + +.vscode-dark .stpa-edge[aspect="3"], +.vscode-dark .stpa-edge-arrow[aspect="3"] { + stroke: var(--stpa-responsibility-dark); +} + +.vscode-dark .stpa-edge[aspect="4"], +.vscode-dark .stpa-edge-arrow[aspect="4"] { + stroke: var(--stpa-uca-dark); +} + +.vscode-dark .stpa-edge[aspect="5"], +.vscode-dark .stpa-edge-arrow[aspect="5"] { + stroke: var(--stpa-cont-constraint-dark); +} + +.vscode-dark .stpa-edge[aspect="6"], +.vscode-dark .stpa-edge-arrow[aspect="6"] { + stroke: var(--stpa-scenario-dark); +} + +.vscode-dark .stpa-edge[aspect="7"], +.vscode-dark .stpa-edge-arrow[aspect="7"] { + stroke: var(--stpa-safety-requirement-dark); +} + +/* Light theme */ +.vscode-light .stpa-node[aspect="0"] { + fill: var(--light-node); + stroke: var(--stpa-loss-light); + stroke-width: 2; +} + +.vscode-light .stpa-node[aspect="1"] { + fill: var(--light-node); + stroke: var(--stpa-hazard-light); + stroke-width: 2; +} + +.vscode-light .stpa-node[aspect="2"] { + fill: var(--light-node); + stroke: var(--stpa-sys-constraint-light); + stroke-width: 2; +} + +.vscode-light .stpa-node[aspect="3"] { + fill: var(--light-node); + stroke: var(--stpa-responsibility-light); + stroke-width: 2; +} + +.vscode-light .stpa-node[aspect="4"] { + fill: var(--light-node); + stroke: var(--stpa-uca-light); + stroke-width: 2; +} + +.vscode-light .stpa-node[aspect="5"] { + fill: var(--light-node); + stroke: var(--stpa-cont-constraint-light); + stroke-width: 2; +} + +.vscode-light .stpa-node[aspect="6"] { + fill: var(--light-node); + stroke: var(--stpa-scenario-light); + stroke-width: 2; +} + +.vscode-light .stpa-node[aspect="7"] { + fill: var(--light-node); + stroke: var(--stpa-safety-requirement-light); + stroke-width: 2; +} + + +.vscode-light .stpa-edge-arrow[aspect="0"] { + fill: var(--stpa-loss-light); +} + +.vscode-light .stpa-edge-arrow[aspect="1"] { + fill: var(--stpa-hazard-light); +} + +.vscode-light .stpa-edge-arrow[aspect="2"] { + fill: var(--stpa-sys-constraint-light); +} + +.vscode-light .stpa-edge-arrow[aspect="3"] { + fill: var(--stpa-responsibility-light); +} + +.vscode-light .stpa-edge-arrow[aspect="4"] { + fill: var(--stpa-uca-light); +} + +.vscode-light .stpa-edge-arrow[aspect="5"] { + fill: var(--stpa-cont-constraint-light); +} + +.vscode-light .stpa-edge-arrow[aspect="6"] { + fill: var(--stpa-scenario-light); +} + +.vscode-light .stpa-edge-arrow[aspect="7"] { + fill: var(--stpa-safety-requirement-light); +} + + +.vscode-light .stpa-edge[aspect="0"], +.vscode-light .stpa-edge-arrow[aspect="0"] { + stroke: var(--stpa-loss-light); +} + +.vscode-light .stpa-edge[aspect="1"], +.vscode-light .stpa-edge-arrow[aspect="1"] { + stroke: var(--stpa-hazard-light); +} + +.vscode-light .stpa-edge[aspect="2"], +.vscode-light .stpa-edge-arrow[aspect="2"] { + stroke: var(--stpa-sys-constraint-light); +} + +.vscode-light .stpa-edge[aspect="3"], +.vscode-light .stpa-edge-arrow[aspect="3"] { + stroke: var(--stpa-responsibility-light); +} + +.vscode-light .stpa-edge[aspect="4"], +.vscode-light .stpa-edge-arrow[aspect="4"] { + stroke: var(--stpa-uca-light); +} + +.vscode-light .stpa-edge[aspect="5"], +.vscode-light .stpa-edge-arrow[aspect="5"] { + stroke: var(--stpa-cont-constraint-light); +} + +.vscode-light .stpa-edge[aspect="6"], +.vscode-light .stpa-edge-arrow[aspect="6"] { + stroke: var(--stpa-scenario-light); +} + +.vscode-light .stpa-edge[aspect="7"], +.vscode-light .stpa-edge-arrow[aspect="7"] { + stroke: var(--stpa-safety-requirement-light); +} \ No newline at end of file diff --git a/extension/src-webview/css/theme.css b/extension/src-webview/css/theme.css index 9615958..1caacb5 100644 --- a/extension/src-webview/css/theme.css +++ b/extension/src-webview/css/theme.css @@ -35,6 +35,12 @@ --stpa-color-sidebar-icon-primary: hsl(0, 0%, 24%); --stpa-color-primary: #0552b5; */ + + /* black/white colors */ + --dark-node: #ffffff00; + --light-node: white; + --highlight-node: #FF2828; + /* Colors for stpa aspects */ --stpa-loss-dark: #AD0000; --stpa-hazard-dark: #A85400; @@ -45,9 +51,6 @@ --stpa-scenario-dark: #616100; --stpa-safety-requirement-dark: #2f4f4f; - --stpa-dark-node: #ffffff00; - --stpa-light-node: white; - --stpa-loss-light: #FF2828; --stpa-hazard-light: #ffa852; --stpa-sys-constraint-light: #00eb00; diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts index 5471be9..14fdfea 100644 --- a/extension/src-webview/di.config.ts +++ b/extension/src-webview/di.config.ts @@ -68,7 +68,7 @@ import { PortView, STPAGraphView, STPANodeView, -} from "./views"; +} from "./stpa-views"; const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); @@ -87,21 +87,23 @@ const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => // configure the diagram elements const context = { bind, unbind, isBound, rebind }; + configureModelElement(context, "label", SLabel, SLabelView); + configureModelElement(context, "label:xref", SLabel, SLabelView); + configureModelElement(context, "html", HtmlRoot, HtmlRootView); + configureModelElement(context, "pre-rendered", PreRenderedElement, PreRenderedView); + + // STPA configureModelElement(context, "graph", SGraph, STPAGraphView); configureModelElement(context, DUMMY_NODE_TYPE, CSNode, CSNodeView); configureModelElement(context, CS_NODE_TYPE, CSNode, CSNodeView); configureModelElement(context, STPA_NODE_TYPE, STPANode, STPANodeView); configureModelElement(context, PARENT_TYPE, SNode, CSNodeView); - configureModelElement(context, "label", SLabel, SLabelView); - configureModelElement(context, "label:xref", SLabel, SLabelView); configureModelElement(context, STPA_EDGE_TYPE, STPAEdge, PolylineArrowEdgeView); configureModelElement(context, STPA_INTERMEDIATE_EDGE_TYPE, STPAEdge, IntermediateEdgeView); configureModelElement(context, CS_EDGE_TYPE, CSEdge, PolylineArrowEdgeView); configureModelElement(context, STPA_PORT_TYPE, STPAPort, PortView); - configureModelElement(context, "html", HtmlRoot, HtmlRootView); - configureModelElement(context, "pre-rendered", PreRenderedElement, PreRenderedView); - //FTA + // FTA configureModelElement(context, FTA_EDGE_TYPE, FTAEdge, PolylineArrowEdgeViewFTA); configureModelElement(context, FTA_NODE_TYPE, FTANode, FTANodeView); }); diff --git a/extension/src-webview/di.symbols.ts b/extension/src-webview/di.symbols.ts index a801967..3d36d19 100644 --- a/extension/src-webview/di.symbols.ts +++ b/extension/src-webview/di.symbols.ts @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2021 by + * Copyright 2021-2023 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -15,7 +15,7 @@ * SPDX-License-Identifier: EPL-2.0 */ -/** DI Symbols for Services provided by the STPA-DSL DI container. */ +/** DI Symbols for Services provided by the PASTA DI container. */ export const DISymbol = { Sidebar: Symbol("Sidebar"), SidebarPanel: Symbol("SidebarPanel"), @@ -25,4 +25,4 @@ export const DISymbol = { OptionsRegistry: Symbol("OptionsRegistry"), RenderOptionsRegistry: Symbol("RenderOptionsRegistry"), CutSetsRegistry: Symbol("CutSetsRegistry"), -}; \ No newline at end of file +}; diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index 5e831c0..b07850e 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -18,38 +18,31 @@ /** @jsx svg */ import { inject, injectable } from 'inversify'; import { VNode } from "snabbdom"; -import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SEdge, SNode, svg } from 'sprotty'; +import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, svg } from 'sprotty'; import { DISymbol } from "./di.symbols"; -import { FTAEdge, FTANode, FTA_EDGE_TYPE, FTNodeType } from './fta-model'; +import { FTAEdge, FTANode, FTNodeType } from './fta-model'; import { CutSetsRegistry } from './options/cut-set-registry'; import { renderAndGate, renderCircle, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "./views-rendering"; +// TODO: combine with STPA methods ?? + @injectable() export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { - protected renderLine(edge: SEdge, segments: Point[], context: RenderingContext): VNode { + protected renderLine(edge: FTAEdge, segments: Point[], context: RenderingContext): VNode { const firstPoint = segments[0]; let path = `M ${firstPoint.x},${firstPoint.y}`; for (let i = 1; i < segments.length; i++) { const p = segments[i]; path += ` L ${p.x},${p.y}`; } - - if ((edge.target as FTANode).highlight === true) { - (edge as FTAEdge).highlight = true; - } else { - (edge as FTAEdge).highlight = false; - } - // if an FTANode is selected, the components not connected to it should fade out - const hidden = edge.type === FTA_EDGE_TYPE && !(edge as FTAEdge).highlight; - - return ; + edge.highlight = (edge.target as FTANode).highlight; + return ; } } - @injectable() export class FTANodeView extends RectangularNodeView { @@ -85,86 +78,60 @@ export class FTANodeView extends RectangularNodeView { break; } - //highlight every node that is in the selected cut set or on the path to the top event. - let set = this.cutSetsRegistry.getCurrentValue(); - let onlyInCutSet = false; - if (set !== undefined) { - //highlight all when the empty cut set is selected - if (set === '-') { - node.highlight = true; - } else { - //unhighlight every node first and then only highlight the correct ones. - node.highlight = false; - if (node.nodeType === FTNodeType.COMPONENT || node.nodeType === FTNodeType.CONDITION) { - //node is component or condition and in the selected cut set. - if (set.includes(node.name)) { - node.highlight = true; - onlyInCutSet = true; - - } else { - //all other components and conditions are not highlighted. - node.highlight = false; - onlyInCutSet = false; - } - } else { - //check if a gate should be highlighted - if (this.checkIfHighlighted(node, set) === true) { - node.highlight = true; - } else { - node.highlight = false; - } - } + // if a cut set is selected, highlight the nodes in it and the connected elements + const set = this.cutSetsRegistry.getCurrentValue(); + let highlight = false; + if (set) { + switch (node.nodeType) { + case FTNodeType.COMPONENT: + case FTNodeType.CONDITION: + // components and conditions should be highlighted when included in the selected cut set + const included = set.includes(node.name); + node.highlight = included; + highlight = included; + break; + default: + // gates are hidden (greyed out) when not connected to shown elements + node.highlight = this.connectedToShown(node, set); + break; } + } else { + node.highlight = true; } - - //if an FTANode is selected, the components not connected to it should fade out - const hidden = !node.highlight; - + // TODO: replace highlight attribute with hidden return - {element} + class-greyed-out={!node.highlight}> + {element} {context.renderChildren(node)} ; } /** - * Takes a node and checks if it is connected to a highlighted node. - * @param node The node we want to check. + * Checks whether the given {@code node} is connected to a highlighted node. + * @param node The node that should be checked. * @param set The set of all highlighted nodes. - * @returns True if the node is connected to a node from the set or false otherwise. + * @returns true if the node is connected to a node from the {@code set} or false otherwise. */ - checkIfHighlighted(node: FTANode, set: any): boolean { + connectedToShown(node: FTANode, set: any): boolean { + // TODO: call this method only one time at the top node and highlight everything on the way that should be highlighted for (const edge of node.outgoingEdges) { - let target = (edge.target as FTANode); - if ((target.nodeType === FTNodeType.COMPONENT || target.nodeType === FTNodeType.CONDITION)) { - if (set.includes(target.name)) { - return true; - } - } else { - if (this.checkIfHighlighted(target, set) === true) { - return true; - } + const target = (edge.target as FTANode); + switch (target.nodeType) { + case FTNodeType.COMPONENT: + case FTNodeType.CONDITION: + if (set.includes(target.name)) { + return true; + } + break; + default: + if (this.connectedToShown(target, set)) { + return true; + } + break; } } return false; } } - -@injectable() -export class FTAGraphView extends RectangularNodeView { - - render(node: SNode, context: RenderingContext): VNode { - - return - - {context.renderChildren(node)} - ; - } -} \ No newline at end of file diff --git a/extension/src-webview/helper-methods.ts b/extension/src-webview/helper-methods.ts index 61de4c5..5af3b77 100644 --- a/extension/src-webview/helper-methods.ts +++ b/extension/src-webview/helper-methods.ts @@ -18,7 +18,6 @@ import { SEdge, SModelElement, SNode } from "sprotty"; import { PortSide, STPAAspect, STPAEdge, STPANode, STPAPort, STPA_EDGE_TYPE, STPA_INTERMEDIATE_EDGE_TYPE, STPA_NODE_TYPE, STPA_PORT_TYPE } from "./stpa-model"; - /** * Collects all children of the nodes in {@code nodes}. * @param nodes The nodes, which children should be selected. @@ -249,4 +248,3 @@ export function flagSameAspect(selected: STPANode): STPANode[] { } return elements; } - diff --git a/extension/src-webview/options/actions.ts b/extension/src-webview/options/actions.ts index 945165b..37e3575 100644 --- a/extension/src-webview/options/actions.ts +++ b/extension/src-webview/options/actions.ts @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2021 by + * Copyright 2021-2023 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -129,16 +129,16 @@ export namespace SendConfigAction { } } - +/** Contains the cut sets for a fault tree */ export interface SendCutSetAction extends Action { kind: typeof SendCutSetAction.KIND; - cutSets: { value: any; }[]; + cutSets: string[]; } export namespace SendCutSetAction { - export const KIND = "sendCutSet"; + export const KIND = "sendCutSets"; - export function create(cutSets: { value: any; }[]): SendCutSetAction { + export function create(cutSets: string[]): SendCutSetAction { return { kind: KIND, cutSets diff --git a/extension/src-webview/options/cut-set-panel.tsx b/extension/src-webview/options/cut-set-panel.tsx index 42d9c53..2982e54 100644 --- a/extension/src-webview/options/cut-set-panel.tsx +++ b/extension/src-webview/options/cut-set-panel.tsx @@ -25,6 +25,7 @@ import { SidebarPanel } from "../sidebar"; import { CutSetsRegistry } from "./cut-set-registry"; import { OptionsRenderer } from "./options-renderer"; +// TODO: extra panel needed? should not be shown for stpa diagrams @injectable() export class CutSetPanel extends SidebarPanel { @@ -40,22 +41,23 @@ export class CutSetPanel extends SidebarPanel { } get title(): string { - return "Cut sets"; + return "Cut Sets"; } render(): VNode { return (
-
Cut sets
+
Cut Sets
{this.optionsRenderer.renderRenderOptions( - this.cutSetsRegistry.allOptions + this.cutSetsRegistry.allCutSets )}
); } + // TODO: other icon? get icon(): VNode { return ; } diff --git a/extension/src-webview/options/cut-set-registry.ts b/extension/src-webview/options/cut-set-registry.ts index 74a3793..7ab6106 100644 --- a/extension/src-webview/options/cut-set-registry.ts +++ b/extension/src-webview/options/cut-set-registry.ts @@ -22,20 +22,24 @@ import { Registry } from "../base/registry"; import { SendCutSetAction } from "./actions"; import { DropDownOption, TransformationOptionType } from "./option-models"; + +const noSelectedCutSet = { displayName: "---", id: "---" }; + +// TODO: should be a synthesis option ?? export class DropDownMenuOption implements DropDownOption { - static readonly ID: string = "cut-sets"; - static readonly NAME: string = "Cut Sets"; - readonly id: string = DropDownMenuOption.ID; - readonly currentId: string = DropDownMenuOption.ID; - readonly name: string = DropDownMenuOption.NAME; - readonly type: TransformationOptionType = TransformationOptionType.DROPDOWN; - values: { displayName: string; id: string }[] = [{ displayName: "---", id: "---" }]; - availableValues: { displayName: string; id: string }[] = [{ displayName: "---", id: "---" }]; - readonly initialValue: { displayName: string; id: string } = { displayName: "---", id: "---" }; - currentValue = { displayName: "---", id: "---" }; + static readonly ID = "cut-sets"; + static readonly NAME = "Cut Sets"; + readonly id = DropDownMenuOption.ID; + readonly currentId = DropDownMenuOption.ID; + readonly name = DropDownMenuOption.NAME; + readonly type = TransformationOptionType.DROPDOWN; + values = [noSelectedCutSet]; + availableValues = [noSelectedCutSet]; + readonly initialValue = noSelectedCutSet; + currentValue = noSelectedCutSet; } -/** {@link Registry} that stores and updates different render options. */ +/** {@link Registry} that stores and updates cut set options. */ @injectable() export class CutSetsRegistry extends Registry { private _options: Map = new Map(); @@ -44,7 +48,7 @@ export class CutSetsRegistry extends Registry { if (SendCutSetAction.isThisAction(action)) { const dropDownOption = new DropDownMenuOption(); for (const set of action.cutSets) { - dropDownOption.availableValues.push({ displayName: set.value, id: set.value }); + dropDownOption.availableValues.push({ displayName: set, id: set }); } this._options.set("cut-sets", dropDownOption); @@ -53,23 +57,24 @@ export class CutSetsRegistry extends Registry { return UpdateModelAction.create([], { animate: false, cause: action }); } - get allOptions(): DropDownOption[] { + get allCutSets(): DropDownOption[] { return Array.from(this._options.values()); } - getCurrentValue(): any { - //if the cut sets were not requested yet, there is nothing to highlight + getCurrentValue(): string[] | undefined { + // if the cut sets were not requested yet, there are no cut sets to display if (this._options.get("cut-sets")?.availableValues.length === 1) { return undefined; } const selectedCutSet: { displayName: string; id: string } = this._options.get("cut-sets")?.currentValue; if (selectedCutSet) { - //slice the brackets at the start and at the end. + // slice the brackets at the start and at the end. const selected = selectedCutSet.displayName.slice(1, -1); if (selected === "-") { - return "-"; + return undefined; } else { - return selected.split(", "); + // determine the names in the cut set + return selected.split(",").map(element => element.trim()); } } } diff --git a/extension/src-webview/options/options-module.ts b/extension/src-webview/options/options-module.ts index dfe258d..e9d94dd 100644 --- a/extension/src-webview/options/options-module.ts +++ b/extension/src-webview/options/options-module.ts @@ -43,7 +43,6 @@ export const optionsModule = new ContainerModule((bind, _, isBound) => { bind(TYPES.IActionHandlerInitializer).toService(DISymbol.OptionsRegistry); bind(DISymbol.RenderOptionsRegistry).to(RenderOptionsRegistry).inSingletonScope(); - bind(DISymbol.CutSetsRegistry).to(CutSetsRegistry).inSingletonScope(); const ctx = { bind, isBound }; diff --git a/extension/src-webview/options/options-renderer.tsx b/extension/src-webview/options/options-renderer.tsx index 3f957ef..a9f37cd 100644 --- a/extension/src-webview/options/options-renderer.tsx +++ b/extension/src-webview/options/options-renderer.tsx @@ -208,12 +208,12 @@ export class OptionsRenderer { ); default: @@ -227,8 +227,8 @@ export class OptionsRenderer { this.actionDispatcher.dispatch(SetRenderOptionAction.create(option.id, newValue)); } - private setCurrentValueOnChange(option:RenderOption, newValue:any){ - option.currentValue = {displayName: newValue, id: newValue} + private setCurrentValueOnChange(option: RenderOption, newValue: any): void { + option.currentValue = { displayName: newValue, id: newValue }; this.actionDispatcher.dispatch(UpdateModelAction.create([], { animate: false })); } } diff --git a/extension/src-webview/views.tsx b/extension/src-webview/stpa-views.tsx similarity index 97% rename from extension/src-webview/views.tsx rename to extension/src-webview/stpa-views.tsx index 38b6e49..b2204be 100644 --- a/extension/src-webview/views.tsx +++ b/extension/src-webview/stpa-views.tsx @@ -58,7 +58,7 @@ export class PolylineArrowEdgeView extends PolylineEdgeView { aspect = (edge as STPAEdge).aspect % 2 === 0 || !lessColoredEdge ? (edge as STPAEdge).aspect : (edge as STPAEdge).aspect - 1; } return ; + class-feedback-edge={feedbackEdge} class-greyed-out={hidden} aspect={aspect} d={path} />; } protected renderAdditionals(edge: SEdge, segments: Point[], context: RenderingContext): VNode[] { @@ -78,7 +78,7 @@ export class PolylineArrowEdgeView extends PolylineEdgeView { aspect = (edge as STPAEdge).aspect % 2 === 0 || !lessColoredEdge ? (edge as STPAEdge).aspect : (edge as STPAEdge).aspect - 1; } return [ - ]; @@ -193,7 +193,7 @@ export class STPANodeView extends RectangularNodeView { class-sprotty-node={sprottyNode} class-sprotty-port={node instanceof SPort} class-mouseover={node.hoverFeedback} - class-hidden={hidden}> + class-greyed-out={hidden}> {element} {context.renderChildren(node)} ; diff --git a/extension/src/actions.ts b/extension/src/actions.ts index 4feec7d..7313715 100644 --- a/extension/src/actions.ts +++ b/extension/src/actions.ts @@ -61,17 +61,17 @@ export namespace GenerateSVGsAction { } } - - +// TODO: better type for cut sets +/** Contains the cut sets for a fault tree */ export interface SendCutSetAction extends Action { kind: typeof SendCutSetAction.KIND; - cutSets: { value: any; }[]; + cutSets: string[]; } export namespace SendCutSetAction { - export const KIND = "sendCutSet"; + export const KIND = "sendCutSets"; - export function create(cutSets: { value: any; }[]): SendCutSetAction { + export function create(cutSets: string[]): SendCutSetAction { return { kind: KIND, cutSets diff --git a/extension/src/extension.ts b/extension/src/extension.ts index fc8c64b..6cda7cf 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -189,18 +189,19 @@ function registerSTPACommands(manager: StpaLspVscodeExtension, context: vscode.E ); } +// TODO: inspect /** - * Sends the cut sets to webview as a SendCutSetAction so that they can be displayed in a dropdown menu. - * @param cutSets The (minimal) cut sets of the current Fault Tree. - */ + * Sends the cut sets to webview as a SendCutSetAction so that they can be displayed in a dropdown menu. + * @param cutSets The (minimal) cut sets of the current Fault Tree. + */ function dispatchCutSetsToWebview(manager: StpaLspVscodeExtension, cutSets:string):void{ cutSets = cutSets.substring(cutSets.indexOf("[")); cutSets = cutSets.slice(1,-2); const cutSetArray = cutSets.split(",\n"); - const cutSetsList: { value: any; }[] = []; + const cutSetsList: string[] = []; for(const set of cutSetArray){ - cutSetsList.push({value: set}); + cutSetsList.push(set); } manager.endpoints.find(endpoint => endpoint.diagramIdentifier?.diagramType === 'fta')?.sendAction({ kind: SendCutSetAction.KIND, cutSets: cutSetsList } as SendCutSetAction); } diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index edabcbf..f5426a0 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -115,7 +115,10 @@ export class StpaLspVscodeExtension extends LspWebviewPanelManager { // send the changes to the language server const changes = changeEvent.contentChanges; const uri = changeEvent.document.uri.toString(); - this.languageClient.sendNotification("editor/textChange", { changes: changes, uri: uri }); + // TODO: ID enforcer for FTA + if (uri.endsWith('.stpa')){ + this.languageClient.sendNotification("editor/textChange", { changes: changes, uri: uri }); + } } createContextTable(context: vscode.ExtensionContext): void { From c85553d41a87353e9d286610d0be1c695658a305 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Mon, 25 Sep 2023 09:14:45 +0200 Subject: [PATCH 48/61] revision (extension) --- .../fta/analysis/fta-cutSet-calculator.ts | 6 +- .../fta/fta-message-handler.ts | 9 +- extension/src-language-server/fta/utils.ts | 23 ++-- extension/src-webview/stpa-views.tsx | 4 +- extension/src-webview/views-rendering.tsx | 81 ++++++------ extension/src/actions.ts | 2 +- extension/src/extension.ts | 119 +++++++++--------- extension/src/language-extension.ts | 4 +- extension/src/utils.ts | 25 +++- extension/src/wview.ts | 2 - 10 files changed, 145 insertions(+), 130 deletions(-) diff --git a/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts b/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts index 1581667..c103cb5 100644 --- a/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts +++ b/extension/src-language-server/fta/analysis/fta-cutSet-calculator.ts @@ -34,9 +34,9 @@ import { namedFtaElement } from "../utils"; * @param allNodes All nodes in the fault tree. * @returns the minimal cut sets for a fault tree. */ -export function determineMinimalCutSet(allNodes: AstNode[]): Set[] { +export function determineMinimalCutSets(allNodes: AstNode[]): Set[] { // TODO: add minimal flag (could reduce computation cost) - const allCutSets = generateCutSetsForFT(allNodes); + const allCutSets = determineCutSetsForFT(allNodes); // Cut sets are minimal if removing one element destroys the cut set // If cut set contains another cut set from the array, remove it since it is not minimal @@ -71,7 +71,7 @@ function checkMinimalCutSet(cutSet: Set, allCutSets: Set[] { +export function determineCutSetsForFT(allNodes: AstNode[]): Set[] { /* Idea: Start from the top event. Get the only child of top event (will always be only one) as our starting node. diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index f121fc5..56839aa 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -18,7 +18,7 @@ import { LangiumSprottySharedServices } from "langium-sprotty"; import { Connection } from "vscode-languageserver"; import { getFTAModel } from "../utils"; -import { determineMinimalCutSet, generateCutSetsForFT } from "./analysis/fta-cutSet-calculator"; +import { determineMinimalCutSets, determineCutSetsForFT } from "./analysis/fta-cutSet-calculator"; import { FtaServices } from "./fta-module"; import { cutSetsToString } from "./utils"; @@ -44,14 +44,13 @@ function addCutSetsHandler(connection: Connection, sharedServices: LangiumSprott connection.onRequest("generate/getCutSets", async (uri: string) => { const model = await getFTAModel(uri, sharedServices); const nodes = [model.topEvent, ...model.components, ...model.conditions, ...model.gates]; - const cutSets = generateCutSetsForFT(nodes); + const cutSets = determineCutSetsForFT(nodes); return cutSetsToString(cutSets); }); - connection.onRequest("generate/getMinimalCutSets", async (uri: string) => { const model = await getFTAModel(uri, sharedServices); const nodes = [model.topEvent, ...model.components, ...model.conditions, ...model.gates]; - const minimalCutSets = determineMinimalCutSet(nodes); - return cutSetsToString(minimalCutSets, true); + const minimalCutSets = determineMinimalCutSets(nodes); + return cutSetsToString(minimalCutSets); }); } diff --git a/extension/src-language-server/fta/utils.ts b/extension/src-language-server/fta/utils.ts index cd4e023..b1524ea 100644 --- a/extension/src-language-server/fta/utils.ts +++ b/extension/src-language-server/fta/utils.ts @@ -20,17 +20,16 @@ import { Component, Condition, Gate, TopEvent } from "../generated/ast"; export type namedFtaElement = Component | Condition | Gate | TopEvent; /** - * Translates the given {@code cutSets} to a string. - * @param cutSets The cut sets to translate. - * @param minimal Determines whether the given {@code cutSets} are minimal. - * @returns a string that contains every cut set. + * Translates the cut sets in the given {@code cutSets} to lists. + * @param cutSets The list containing the cut sets to translate. + * @returns a list containing the cut sets in {@code cutSets} as a list. */ -export function cutSetsToString(cutSets: Set[], minimal?: boolean): string { - let text = `The resulting ${cutSets.length}`; - if (minimal) { - text += ` minimal`; +export function cutSetsToString(cutSets: Set[]): string[][] { + const result: string[][] = []; + for (const set of cutSets) { + const newSet: string[] = []; + set.forEach((element) => newSet.push(element.name)); + result.push(newSet); } - text += ` cut sets are:\n`; - text += `[${cutSets.map((cutSet) => `[${[...cutSet].map((element) => element.name).join(", ")}]`).join(",\n")}]`; - return text; -} \ No newline at end of file + return result; +} diff --git a/extension/src-webview/stpa-views.tsx b/extension/src-webview/stpa-views.tsx index b2204be..3345d51 100644 --- a/extension/src-webview/stpa-views.tsx +++ b/extension/src-webview/stpa-views.tsx @@ -22,14 +22,12 @@ import { IView, IViewArgs, Point, PolylineEdgeView, RectangularNodeView, Renderi import { DISymbol } from './di.symbols'; import { collectAllChildren } from './helper-methods'; import { ColorStyleOption, DifferentFormsOption, RenderOptionsRegistry } from './options/render-options-registry'; -import { CSEdge, CS_EDGE_TYPE, CS_NODE_TYPE, EdgeType, PARENT_TYPE, STPAAspect, STPAEdge, STPANode, STPA_EDGE_TYPE, STPA_INTERMEDIATE_EDGE_TYPE, STPA_NODE_TYPE } from './stpa-model'; +import { CSEdge, CS_EDGE_TYPE, EdgeType, STPAAspect, STPAEdge, STPANode, STPA_EDGE_TYPE, STPA_INTERMEDIATE_EDGE_TYPE } from './stpa-model'; import { renderCircle, renderDiamond, renderHexagon, renderMirroredTriangle, renderPentagon, renderRectangle, renderRoundedRectangle, renderTrapez, renderTriangle } from './views-rendering'; - /** Determines if path/aspect highlighting is currently on. */ let highlighting: boolean; - @injectable() export class PolylineArrowEdgeView extends PolylineEdgeView { diff --git a/extension/src-webview/views-rendering.tsx b/extension/src-webview/views-rendering.tsx index fa5abdb..bc978de 100644 --- a/extension/src-webview/views-rendering.tsx +++ b/extension/src-webview/views-rendering.tsx @@ -3,7 +3,7 @@ * * http://rtsys.informatik.uni-kiel.de/kieler * - * Copyright 2021 by + * Copyright 2021-2023 by * + Kiel University * + Department of Computer Science * + Real-Time and Embedded Systems Group @@ -21,7 +21,7 @@ import { SNode, svg } from 'sprotty'; /** * Creates a circle for {@code node}. - * @param node The node whch should be represented by a circle. + * @param node The node that should be represented by a circle. * @returns A circle for {@code node}. */ export function renderCircle(node: SNode): VNode { @@ -33,7 +33,7 @@ export function renderCircle(node: SNode): VNode { /** * Creates a rectangle for {@code node}. - * @param node The node whch should be represented by a rectangle. + * @param node The node that should be represented by a rectangle. * @returns A rectangle for {@code node}. */ export function renderRectangle(node: SNode): VNode { @@ -45,7 +45,7 @@ export function renderRectangle(node: SNode): VNode { /** * Creates a rounded rectangle for {@code node}. - * @param node The node whch should be represented by a rounded rectangle. + * @param node The node that should be represented by a rounded rectangle. * @returns A rounded rectangle for {@code node}. */ export function renderRoundedRectangle(node: SNode): VNode { @@ -59,7 +59,7 @@ export function renderRoundedRectangle(node: SNode): VNode { /** * Creates a triangle for {@code node}. - * @param node The node whch should be represented by a triangle. + * @param node The node that should be represented by a triangle. * @returns A triangle for {@code node}. */ export function renderTriangle(node: SNode): VNode { @@ -76,7 +76,7 @@ export function renderTriangle(node: SNode): VNode { /** * Creates a mirrored triangle for {@code node}. - * @param node The node whch should be represented by a mirrored triangle. + * @param node The node that should be represented by a mirrored triangle. * @returns A mrrored triangle for {@code node}. */ export function renderMirroredTriangle(node: SNode): VNode { @@ -93,7 +93,7 @@ export function renderMirroredTriangle(node: SNode): VNode { /** * Creates a trapez for {@code node}. - * @param node The node whch should be represented by a trapez. + * @param node The node that should be represented by a trapez. * @returns A trapez for {@code node}. */ export function renderTrapez(node: SNode): VNode { @@ -112,7 +112,7 @@ export function renderTrapez(node: SNode): VNode { /** * Creates a diamond for {@code node}. - * @param node The node whch should be represented by a diamond. + * @param node The node that should be represented by a diamond. * @returns A diamond for {@code node}. */ export function renderDiamond(node: SNode): VNode { @@ -131,7 +131,7 @@ export function renderDiamond(node: SNode): VNode { /** * Creates a pentagon for {@code node}. - * @param node The node whch should be represented by a pentagon. + * @param node The node that should be represented by a pentagon. * @returns A pentagon for {@code node}. */ export function renderPentagon(node: SNode): VNode { @@ -152,7 +152,7 @@ export function renderPentagon(node: SNode): VNode { /** * Creates a hexagon for {@code node}. - * @param node The node whch should be represented by a hexagon. + * @param node The node that should be represented by a hexagon. * @returns A hexagon for {@code node}. */ export function renderHexagon(node: SNode): VNode { @@ -172,89 +172,88 @@ export function renderHexagon(node: SNode): VNode { /** * Creates an And-Gate for {@code node}. - * @param node The node whch should be represented by an And-Gate. + * @param node The node that should be represented by an And-Gate. * @returns An And-Gate for {@code node}. */ -export function renderAndGate(node:SNode): VNode{ +export function renderAndGate(node: SNode): VNode { const leftX = 0; const rightX = Math.max(node.size.width, 0); - const midX = Math.max(node.size.width, 0) / 2.0; const botY = Math.max(node.size.height, 0); const midY = Math.max(node.size.height, 0) / 2.0; const topY = 0; - const d = 'M' + leftX + " " + botY + " L " + leftX + " " + midY + " C " + leftX + " " + topY + " " + rightX + " " + topY + " " + rightX + " " + midY + - " L " + rightX + " " + botY + 'Z'; + const d = 'M' + leftX + " " + botY + " L " + leftX + " " + midY + " C " + leftX + " " + topY + " " + rightX + " " + + topY + " " + rightX + " " + midY + " L " + rightX + " " + botY + 'Z'; - return ; } /** * Creates an Or-Gate for {@code node}. - * @param node The node whch should be represented by an Or-Gate. + * @param node The node that should be represented by an Or-Gate. * @returns An Or-Gate for {@code node}. */ -export function renderOrGate(node:SNode): VNode{ +export function renderOrGate(node: SNode): VNode { const leftX = 0; const rightX = Math.max(node.size.width, 0); const midX = rightX / 2.0; const botY = Math.max(node.size.height, 0); - const nearBotY = Math.max(node.size.height, 0) - (Math.max(node.size.height, 0) / 10.0); + const nearBotY = Math.max(node.size.height, 0) - (Math.max(node.size.height, 0) / 10.0); const midY = Math.max(node.size.height, 0) / 2; const topY = 0; - const d = 'M' + leftX + " " + botY + " L " + leftX + " " + midY + " C " + midX + " " + topY + " "+ midX + " " + topY + " " + rightX + " " + midY + - " L " + rightX + " " + botY + " L " + midX + " " + nearBotY + " Z "; + const d = 'M' + leftX + " " + botY + " L " + leftX + " " + midY + " C " + midX + " " + topY + " " + midX + " " + + topY + " " + rightX + " " + midY + " L " + rightX + " " + botY + " L " + midX + " " + nearBotY + " Z "; return + d={d} + />; } /** * Creates an Kn-Gate for {@code node}. - * @param node The node whch should be represented by an Kn-Gate. + * @param node The node that should be represented by an Kn-Gate. * @returns An Kn-Gate for {@code node}. */ -export function renderKnGate(node:SNode, k:number, n:number): VNode{ +export function renderKnGate(node: SNode, k: number, n: number): VNode { const leftX = 0; const rightX = Math.max(node.size.width, 0); const midX = rightX / 2.0; const botY = Math.max(node.size.height, 0); - const nearBotY = Math.max(node.size.height, 0) - (Math.max(node.size.height, 0) / 10.0); + const nearBotY = Math.max(node.size.height, 0) - (Math.max(node.size.height, 0) / 10.0); const midY = Math.max(node.size.height, 0) / 2; const topY = 0; - const d = 'M' + leftX + " " + botY + " L " + leftX + " " + midY + " C " + midX + " " + topY + " "+ midX + " " + topY + " " + rightX + " " + midY + - " L " + rightX + " " + botY + " L " + midX + " " + nearBotY + " Z "; + const d = 'M' + leftX + " " + botY + " L " + leftX + " " + midY + " C " + midX + " " + topY + " " + midX + " " + + topY + " " + rightX + " " + midY + " L " + rightX + " " + botY + " L " + midX + " " + nearBotY + " Z "; return ( - - - {`${k}/${n}`} - + + + {`${k}/${n}`} + - ); + ); } /** * Creates an Inhibit-Gate for {@code node}. - * @param node The node whch should be represented by an Inhibit-Gate. + * @param node The node that should be represented by an Inhibit-Gate. * @returns An Inhibit-Gate for {@code node}. */ -export function renderInhibitGate(node:SNode): VNode{ +export function renderInhibitGate(node: SNode): VNode { const leftX = 0; - const midX = Math.max(node.size.width, 0) / 2.0 ; + const midX = Math.max(node.size.width, 0) / 2.0; const rightX = Math.max(node.size.width, 0); const lowestY = Math.max(node.size.height, 0); const lowY = Math.max(node.size.height, 0) - (Math.max(node.size.height, 0) / 4.0); const highY = Math.max(node.size.height, 0) / 4.0; const highestY = 0; - const d = 'M' + leftX + " " + lowY + " L " + leftX + " " + highY + " L " + midX + " " + highestY + " L " + rightX + " " + highY + - " L " + rightX + " " + lowY + " L " + midX + " " + lowestY + "Z"; + const d = 'M' + leftX + " " + lowY + " L " + leftX + " " + highY + " L " + midX + " " + highestY + " L " + rightX + + " " + highY + " L " + rightX + " " + lowY + " L " + midX + " " + lowestY + "Z"; return + d={d} + />; } \ No newline at end of file diff --git a/extension/src/actions.ts b/extension/src/actions.ts index 7313715..24713be 100644 --- a/extension/src/actions.ts +++ b/extension/src/actions.ts @@ -61,7 +61,7 @@ export namespace GenerateSVGsAction { } } -// TODO: better type for cut sets +// TODO: better type for cut sets? /** Contains the cut sets for a fault tree */ export interface SendCutSetAction extends Action { kind: typeof SendCutSetAction.KIND; diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 6cda7cf..2e63aca 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -21,14 +21,14 @@ import { LspSprottyEditorProvider, LspSprottyViewProvider } from 'sprotty-vscode import * as vscode from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'; import { Messenger } from 'vscode-messenger'; +import { SendCutSetAction } from './actions'; import { command } from './constants'; import { StpaLspVscodeExtension } from './language-extension'; -import { createOutputChannel, createQuickPickForWorkspaceOptions } from './utils'; import { createSTPAResultMarkdownFile } from './report/md-export'; -import { LTLFormula } from './sbm/utils'; -import { createSBMs } from './sbm/sbm-generation'; import { StpaResult } from './report/utils'; -import { SendCutSetAction } from './actions'; +import { createSBMs } from './sbm/sbm-generation'; +import { LTLFormula } from './sbm/utils'; +import { createOutputChannel, createQuickPickForWorkspaceOptions } from './utils'; let languageClient: LanguageClient; @@ -100,42 +100,51 @@ export async function deactivate(): Promise { function registerSTPACommands(manager: StpaLspVscodeExtension, context: vscode.ExtensionContext, options: { extensionPrefix: string; }): void { context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.contextTable.open', async (...commandArgs: any[]) => { - manager.createContextTable(context); - await manager.contextTable.ready(); - const uri = (commandArgs[0] as vscode.Uri).toString(); - languageClient.sendNotification('contextTable/getData', uri); - }) + vscode.commands.registerCommand( + options.extensionPrefix + ".contextTable.open", + async (...commandArgs: any[]) => { + manager.createContextTable(context); + await manager.contextTable.ready(); + const uri = (commandArgs[0] as vscode.Uri).toString(); + languageClient.sendNotification("contextTable/getData", uri); + } + ) ); // commands for toggling the provided validation checks context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.stpa.checks.setCheckResponsibilitiesForConstraints', async () => { - createQuickPickForWorkspaceOptions("checkResponsibilitiesForConstraints"); - }) + vscode.commands.registerCommand( + options.extensionPrefix + ".stpa.checks.setCheckResponsibilitiesForConstraints", + async () => { + createQuickPickForWorkspaceOptions("checkResponsibilitiesForConstraints"); + } + ) ); context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.stpa.checks.checkConstraintsForUCAs', async () => { + vscode.commands.registerCommand(options.extensionPrefix + ".stpa.checks.checkConstraintsForUCAs", async () => { createQuickPickForWorkspaceOptions("checkConstraintsForUCAs"); }) ); context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.stpa.checks.checkScenariosForUCAs', async () => { + vscode.commands.registerCommand(options.extensionPrefix + ".stpa.checks.checkScenariosForUCAs", async () => { createQuickPickForWorkspaceOptions("checkScenariosForUCAs"); }) ); context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.stpa.checks.checkSafetyRequirementsForUCAs', async () => { - createQuickPickForWorkspaceOptions("checkSafetyRequirementsForUCAs"); - }) + vscode.commands.registerCommand( + options.extensionPrefix + ".stpa.checks.checkSafetyRequirementsForUCAs", + async () => { + createQuickPickForWorkspaceOptions("checkSafetyRequirementsForUCAs"); + } + ) ); context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.IDs.undo', async () => { + vscode.commands.registerCommand(options.extensionPrefix + ".IDs.undo", async () => { manager.ignoreNextTextChange = true; vscode.commands.executeCommand("undo"); }) ); context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.IDs.redo', async () => { + vscode.commands.registerCommand(options.extensionPrefix + ".IDs.redo", async () => { manager.ignoreNextTextChange = true; vscode.commands.executeCommand("redo"); }) @@ -143,67 +152,65 @@ function registerSTPACommands(manager: StpaLspVscodeExtension, context: vscode.E // command for creating a pdf context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.stpa.md.creation', async (uri: vscode.Uri) => { - const data: StpaResult = await languageClient.sendRequest('result/getData', uri.toString()); + vscode.commands.registerCommand(options.extensionPrefix + ".stpa.md.creation", async (uri: vscode.Uri) => { + const data: StpaResult = await languageClient.sendRequest("result/getData", uri.toString()); await createSTPAResultMarkdownFile(data, manager); }) ); context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.stpa.SBM.generation', async (uri: vscode.Uri) => { + vscode.commands.registerCommand(options.extensionPrefix + ".stpa.SBM.generation", async (uri: vscode.Uri) => { await manager.lsReady; - const formulas: Record = await languageClient.sendRequest('verification/generateLTL', uri.path); + const formulas: Record = await languageClient.sendRequest( + "verification/generateLTL", + uri.path + ); // controlAction names are just the action without the controller as prefix // generate a safe behavioral model - const controlActions: Record = await languageClient.sendRequest('verification/getControlActions', uri.path); + const controlActions: Record = await languageClient.sendRequest( + "verification/getControlActions", + uri.path + ); createSBMs(controlActions, formulas); }) ); // register commands that other extensions can use - context.subscriptions.push(vscode.commands.registerCommand( - command.getLTLFormula, - async (uri: string) => { + context.subscriptions.push( + vscode.commands.registerCommand(command.getLTLFormula, async (uri: string) => { // generate and send back the LTLs based on the STPA UCAs await manager.lsReady; - const formulas: Record = await languageClient.sendRequest('verification/generateLTL', uri); + const formulas: Record = await languageClient.sendRequest( + "verification/generateLTL", + uri + ); return formulas; - } - )); + }) + ); // commands for computing and displaying the (minimal) cut sets of the fault tree. context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.fta.cutSets', async (uri: vscode.Uri) =>{ - const cutSets:string = await languageClient.sendRequest('generate/getCutSets', uri.path); - //Send cut sets to webview to display them in a dropdown menu. - dispatchCutSetsToWebview(manager, cutSets); - createOutputChannel(cutSets, "FTA Cut Sets"); + vscode.commands.registerCommand(options.extensionPrefix + ".fta.cutSets", async (uri: vscode.Uri) => { + const cutSets: string[][] = await languageClient.sendRequest("generate/getCutSets", uri.path); + handleCutSets(manager, cutSets, false); }) ); context.subscriptions.push( - vscode.commands.registerCommand(options.extensionPrefix + '.fta.minimalCutSets', async (uri: vscode.Uri) =>{ - const minimalCutSets:string = await languageClient.sendRequest('generate/getMinimalCutSets', uri.path); - dispatchCutSetsToWebview(manager, minimalCutSets); - createOutputChannel(minimalCutSets, "FTA Cut Sets"); + vscode.commands.registerCommand(options.extensionPrefix + ".fta.minimalCutSets", async (uri: vscode.Uri) => { + const minimalCutSets: string[][] = await languageClient.sendRequest("generate/getMinimalCutSets", uri.path); + handleCutSets(manager, minimalCutSets, true); }) - ); + ); } -// TODO: inspect -/** - * Sends the cut sets to webview as a SendCutSetAction so that they can be displayed in a dropdown menu. - * @param cutSets The (minimal) cut sets of the current Fault Tree. - */ -function dispatchCutSetsToWebview(manager: StpaLspVscodeExtension, cutSets:string):void{ - cutSets = cutSets.substring(cutSets.indexOf("[")); - cutSets = cutSets.slice(1,-2); - const cutSetArray = cutSets.split(",\n"); - - const cutSetsList: string[] = []; - for(const set of cutSetArray){ - cutSetsList.push(set); - } - manager.endpoints.find(endpoint => endpoint.diagramIdentifier?.diagramType === 'fta')?.sendAction({ kind: SendCutSetAction.KIND, cutSets: cutSetsList } as SendCutSetAction); +function handleCutSets(manager: StpaLspVscodeExtension, cutSets: string[][], minimal?: boolean): void { + const cutSetStrings = cutSets.map((cutSet) => `[${cutSet.join(", ")}]`); + // print cut sets to output channel + createOutputChannel(cutSetStrings, "FTA Cut Sets", minimal); + // send cut sets to the webview + manager.endpoints + .find((endpoint) => endpoint.diagramIdentifier?.diagramType === "fta") + ?.sendAction({ kind: SendCutSetAction.KIND, cutSets: cutSetStrings } as SendCutSetAction); } function createLanguageClient(context: vscode.ExtensionContext): LanguageClient { diff --git a/extension/src/language-extension.ts b/extension/src/language-extension.ts index f5426a0..6b20ab5 100644 --- a/extension/src/language-extension.ts +++ b/extension/src/language-extension.ts @@ -100,8 +100,6 @@ export class StpaLspVscodeExtension extends LspWebviewPanelManager { }); } - - /** * Notifies the language server that a textdocument has changed. * @param changeEvent The change in the text document. @@ -116,7 +114,7 @@ export class StpaLspVscodeExtension extends LspWebviewPanelManager { const changes = changeEvent.contentChanges; const uri = changeEvent.document.uri.toString(); // TODO: ID enforcer for FTA - if (uri.endsWith('.stpa')){ + if (uri.endsWith(".stpa")) { this.languageClient.sendNotification("editor/textChange", { changes: changes, uri: uri }); } } diff --git a/extension/src/utils.ts b/extension/src/utils.ts index 6bd7aa8..525ae85 100644 --- a/extension/src/utils.ts +++ b/extension/src/utils.ts @@ -115,12 +115,29 @@ export class UCA_TYPE { } /** - * Creates an output channel with the given name and prints the given cut sets. + * Creates an output channel with the given name and prints the given cut sets. * @param cutSets The cut sets to print. * @param channelName The name of the channel. */ -export function createOutputChannel(cutSets:string, channelName:string):void{ +export function createOutputChannel(cutSets: string[], channelName: string, minimal?: boolean): void { + const text = cutSetsToString(cutSets, minimal); const outputCutSets = vscode.window.createOutputChannel(channelName); - outputCutSets.append(cutSets); + outputCutSets.append(text); outputCutSets.show(); -} \ No newline at end of file +} + +/** + * Translates the given {@code cutSets} to a string. + * @param cutSets The cut sets to translate. + * @param minimal Determines whether the given {@code cutSets} are minimal. + * @returns a string that contains every cut set. + */ +export function cutSetsToString(cutSets: string[], minimal?: boolean): string { + let text = `The resulting ${cutSets.length}`; + if (minimal) { + text += ` minimal`; + } + text += ` cut sets are:\n`; + text += `[${cutSets.join(",\n")}]`; + return text; +} diff --git a/extension/src/wview.ts b/extension/src/wview.ts index 3ad6567..117eb75 100644 --- a/extension/src/wview.ts +++ b/extension/src/wview.ts @@ -33,7 +33,6 @@ export class StpaLspWebview extends LspWebviewEndpoint { case SelectAction.KIND: this.handleSelectAction(message.action as SelectAction); break; - } } return super.receiveAction(message); @@ -75,5 +74,4 @@ export class StpaLspWebview extends LspWebviewEndpoint { const configOptions = vscode.workspace.getConfiguration("pasta"); action.options.forEach((element) => configOptions.update(element.id, element.value)); } - } From eb5108ad7a155ff5b9d2f831a087c386608be4ac Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Tue, 26 Sep 2023 10:53:31 +0200 Subject: [PATCH 49/61] cut set option as synthesis option --- .../src-language-server/diagram-server.ts | 2 +- .../fta/fta-message-handler.ts | 18 ++++- .../src-language-server/fta/fta-module.ts | 2 +- .../fta/fta-synthesis-options.ts | 77 +++++++++++++++++++ .../fta/synthesis-options.ts | 25 ------ extension/src-language-server/fta/utils.ts | 10 +-- .../stpa/diagram/diagram-generator.ts | 2 +- .../stpa/diagram/filtering.ts | 2 +- ...s-options.ts => stpa-synthesis-options.ts} | 5 -- .../src-language-server/stpa/diagram/utils.ts | 2 +- .../stpa/result-report/svg-generator.ts | 2 +- .../src-language-server/stpa/stpa-module.ts | 2 +- .../src-language-server/synthesis-options.ts | 5 ++ extension/src/extension.ts | 13 +--- 14 files changed, 112 insertions(+), 55 deletions(-) create mode 100644 extension/src-language-server/fta/fta-synthesis-options.ts delete mode 100644 extension/src-language-server/fta/synthesis-options.ts rename extension/src-language-server/stpa/diagram/{synthesis-options.ts => stpa-synthesis-options.ts} (98%) diff --git a/extension/src-language-server/diagram-server.ts b/extension/src-language-server/diagram-server.ts index cf69ae8..5713610 100644 --- a/extension/src-language-server/diagram-server.ts +++ b/extension/src-language-server/diagram-server.ts @@ -28,7 +28,7 @@ import { Connection } from "vscode-languageserver"; import { SetSynthesisOptionsAction, UpdateOptionsAction } from "./options/actions"; import { DropDownOption } from "./options/option-models"; import { GenerateSVGsAction, RequestSvgAction, SvgAction } from "./stpa/actions"; -import { StpaSynthesisOptions, filteringUCAsID } from "./stpa/diagram/synthesis-options"; +import { StpaSynthesisOptions, filteringUCAsID } from "./stpa/diagram/stpa-synthesis-options"; import { COMPLETE_GRAPH_PATH, CONTROL_STRUCTURE_PATH, diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index 56839aa..e251e22 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -32,7 +32,7 @@ export function addFTANotificationHandler( ftaServices: FtaServices, sharedServices: LangiumSprottySharedServices ): void { - addCutSetsHandler(connection, sharedServices); + addCutSetsHandler(connection, ftaServices, sharedServices); } /** @@ -40,17 +40,27 @@ export function addFTANotificationHandler( * @param connection * @param ftaServices */ -function addCutSetsHandler(connection: Connection, sharedServices: LangiumSprottySharedServices): void { +function addCutSetsHandler(connection: Connection, ftaServices: FtaServices, sharedServices: LangiumSprottySharedServices): void { connection.onRequest("generate/getCutSets", async (uri: string) => { const model = await getFTAModel(uri, sharedServices); const nodes = [model.topEvent, ...model.components, ...model.conditions, ...model.gates]; const cutSets = determineCutSetsForFT(nodes); - return cutSetsToString(cutSets); + const cutSetsString = cutSetsToString(cutSets); + const dropdownValues = cutSetsString.map((cutSet) => { + return { displayName: cutSet, id: cutSet }; + }); + ftaServices.options.SynthesisOptions.updateCutSetsOption(dropdownValues); + return cutSetsString; }); connection.onRequest("generate/getMinimalCutSets", async (uri: string) => { const model = await getFTAModel(uri, sharedServices); const nodes = [model.topEvent, ...model.components, ...model.conditions, ...model.gates]; const minimalCutSets = determineMinimalCutSets(nodes); - return cutSetsToString(minimalCutSets); + const cutSetsString = cutSetsToString(minimalCutSets); + const dropdownValues = cutSetsString.map((cutSet) => { + return { displayName: cutSet, id: cutSet }; + }); + ftaServices.options.SynthesisOptions.updateCutSetsOption(dropdownValues); + return cutSetsString; }); } diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index 5fdd444..f38f16a 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -28,7 +28,7 @@ import { import { FtaDiagramGenerator } from "./diagram/fta-diagram-generator"; import { FtaLayoutConfigurator } from "./diagram/fta-layout-config"; import { FtaValidationRegistry, FtaValidator } from "./fta-validator"; -import { FtaSynthesisOptions } from "./synthesis-options"; +import { FtaSynthesisOptions } from "./fta-synthesis-options"; /** * Declaration of custom services. diff --git a/extension/src-language-server/fta/fta-synthesis-options.ts b/extension/src-language-server/fta/fta-synthesis-options.ts new file mode 100644 index 0000000..56be9b7 --- /dev/null +++ b/extension/src-language-server/fta/fta-synthesis-options.ts @@ -0,0 +1,77 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { DropDownOption, TransformationOptionType, ValuedSynthesisOption } from "../options/option-models"; +import { SynthesisOptions } from "../synthesis-options"; + +const cutSetsID = "cutSets"; + +const cutSets: ValuedSynthesisOption = { + synthesisOption: { + id: cutSetsID, + name: "Highlight Cut Set", + type: TransformationOptionType.DROPDOWN, + currentId: "---", + availableValues: [{ displayName: "---", id: "noCutSet" }], + initialValue: "---", + currentValue: "---", + values: [], + } as DropDownOption, + currentValue: "---", +}; + +export class FtaSynthesisOptions extends SynthesisOptions { + constructor() { + super(); + this.options = [cutSets]; + } + + /** + * Updates the cutSets option with the availabe cut sets. + * @param values The currently avaiable cut sets. + */ + updateCutSetsOption(values: { displayName: string; id: string }[]): void { + const option = this.getOption(cutSetsID); + if (option) { + (option.synthesisOption as DropDownOption).availableValues = [ + { displayName: "---", id: "noCutSet" }, + ...values, + ]; + // if the last selected cut set is not available anymore, + // set the option to the first value of the new list + if (!values.find((val) => val.id === (option.synthesisOption as DropDownOption).currentId)) { + (option.synthesisOption as DropDownOption).currentId = values[0].id; + option.synthesisOption.currentValue = values[0].id; + option.synthesisOption.initialValue = values[0].id; + option.currentValue = values[0].id; + } + } + } + + setCutSets(value: string): void { + const option = this.options.find((option) => option.synthesisOption.id === cutSetsID); + if (option) { + option.currentValue = value; + option.synthesisOption.currentValue = value; + (option.synthesisOption as DropDownOption).currentId = value; + } + } + + getCutSets(): string { + return this.getOption(cutSetsID)?.currentValue; + } +} diff --git a/extension/src-language-server/fta/synthesis-options.ts b/extension/src-language-server/fta/synthesis-options.ts deleted file mode 100644 index 9cb3b47..0000000 --- a/extension/src-language-server/fta/synthesis-options.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient - * - * http://rtsys.informatik.uni-kiel.de/kieler - * - * Copyright 2023 by - * + Kiel University - * + Department of Computer Science - * + Real-Time and Embedded Systems Group - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - */ - -import { SynthesisOptions } from "../synthesis-options"; - -export class FtaSynthesisOptions extends SynthesisOptions { - constructor() { - super(); - // this.options = []; - } -} diff --git a/extension/src-language-server/fta/utils.ts b/extension/src-language-server/fta/utils.ts index b1524ea..379657f 100644 --- a/extension/src-language-server/fta/utils.ts +++ b/extension/src-language-server/fta/utils.ts @@ -20,16 +20,16 @@ import { Component, Condition, Gate, TopEvent } from "../generated/ast"; export type namedFtaElement = Component | Condition | Gate | TopEvent; /** - * Translates the cut sets in the given {@code cutSets} to lists. + * Translates the cut sets in the given {@code cutSets} to strings. * @param cutSets The list containing the cut sets to translate. - * @returns a list containing the cut sets in {@code cutSets} as a list. + * @returns a list containing the cut sets in {@code cutSets} as a string. */ -export function cutSetsToString(cutSets: Set[]): string[][] { - const result: string[][] = []; +export function cutSetsToString(cutSets: Set[]): string[] { + const result: string[] = []; for (const set of cutSets) { const newSet: string[] = []; set.forEach((element) => newSet.push(element.name)); - result.push(newSet); + result.push(`[${newSet.join(", ")}]`); } return result; } diff --git a/extension/src-language-server/stpa/diagram/diagram-generator.ts b/extension/src-language-server/stpa/diagram/diagram-generator.ts index 01b2e34..82dbea2 100644 --- a/extension/src-language-server/stpa/diagram/diagram-generator.ts +++ b/extension/src-language-server/stpa/diagram/diagram-generator.ts @@ -48,7 +48,7 @@ import { STPA_NODE_TYPE, STPA_PORT_TYPE, } from "./stpa-model"; -import { StpaSynthesisOptions, labelManagementValue, showLabelsValue } from "./synthesis-options"; +import { StpaSynthesisOptions, labelManagementValue, showLabelsValue } from "./stpa-synthesis-options"; import { createUCAContextDescription, getAspect, getTargets, setLevelOfCSNodes, setLevelsForSTPANodes } from "./utils"; export class StpaDiagramGenerator extends LangiumDiagramGenerator { diff --git a/extension/src-language-server/stpa/diagram/filtering.ts b/extension/src-language-server/stpa/diagram/filtering.ts index 8bbad68..16a5283 100644 --- a/extension/src-language-server/stpa/diagram/filtering.ts +++ b/extension/src-language-server/stpa/diagram/filtering.ts @@ -28,7 +28,7 @@ import { SafetyConstraint, SystemConstraint, } from "../../generated/ast"; -import { StpaSynthesisOptions } from "./synthesis-options"; +import { StpaSynthesisOptions } from "./stpa-synthesis-options"; /** * Needed to work on a filtered model without changing the original model. diff --git a/extension/src-language-server/stpa/diagram/synthesis-options.ts b/extension/src-language-server/stpa/diagram/stpa-synthesis-options.ts similarity index 98% rename from extension/src-language-server/stpa/diagram/synthesis-options.ts rename to extension/src-language-server/stpa/diagram/stpa-synthesis-options.ts index c398ce4..111cc4a 100644 --- a/extension/src-language-server/stpa/diagram/synthesis-options.ts +++ b/extension/src-language-server/stpa/diagram/stpa-synthesis-options.ts @@ -624,9 +624,4 @@ export class StpaSynthesisOptions extends SynthesisOptions { option.synthesisOption.currentValue = value; } } - - protected getOption(id: string): ValuedSynthesisOption | undefined { - const option = this.options.find((option) => option.synthesisOption.id === id); - return option; - } } diff --git a/extension/src-language-server/stpa/diagram/utils.ts b/extension/src-language-server/stpa/diagram/utils.ts index ae4fa59..1d7872a 100644 --- a/extension/src-language-server/stpa/diagram/utils.ts +++ b/extension/src-language-server/stpa/diagram/utils.ts @@ -31,7 +31,7 @@ import { } from "../../generated/ast"; import { STPANode } from "./stpa-interfaces"; import { STPAAspect } from "./stpa-model"; -import { groupValue } from "./synthesis-options"; +import { groupValue } from "./stpa-synthesis-options"; /** * Getter for the references contained in {@code node}. diff --git a/extension/src-language-server/stpa/result-report/svg-generator.ts b/extension/src-language-server/stpa/result-report/svg-generator.ts index 803b849..e7eaab6 100644 --- a/extension/src-language-server/stpa/result-report/svg-generator.ts +++ b/extension/src-language-server/stpa/result-report/svg-generator.ts @@ -16,7 +16,7 @@ */ import { SetSynthesisOptionsAction } from "../../options/actions"; -import { StpaSynthesisOptions } from "../diagram/synthesis-options"; +import { StpaSynthesisOptions } from "../diagram/stpa-synthesis-options"; /* the paths for the several diagrams of the STPA aspects */ export const SVG_PATH = "/images"; diff --git a/extension/src-language-server/stpa/stpa-module.ts b/extension/src-language-server/stpa/stpa-module.ts index 1bbc23d..7150f6b 100644 --- a/extension/src-language-server/stpa/stpa-module.ts +++ b/extension/src-language-server/stpa/stpa-module.ts @@ -35,7 +35,7 @@ import { IDEnforcer } from "./ID-enforcer"; import { ContextTableProvider } from "./contextTable/context-dataProvider"; import { StpaDiagramGenerator } from "./diagram/diagram-generator"; import { StpaLayoutConfigurator } from "./diagram/layout-config"; -import { StpaSynthesisOptions } from "./diagram/synthesis-options"; +import { StpaSynthesisOptions } from "./diagram/stpa-synthesis-options"; import { StpaScopeProvider } from "./stpa-scopeProvider"; import { StpaValidationRegistry, StpaValidator } from "./stpa-validator"; diff --git a/extension/src-language-server/synthesis-options.ts b/extension/src-language-server/synthesis-options.ts index d2ceed8..d81c391 100644 --- a/extension/src-language-server/synthesis-options.ts +++ b/extension/src-language-server/synthesis-options.ts @@ -27,4 +27,9 @@ export class SynthesisOptions { getSynthesisOptions(): ValuedSynthesisOption[] { return this.options; } + + protected getOption(id: string): ValuedSynthesisOption | undefined { + const option = this.options.find((option) => option.synthesisOption.id === id); + return option; + } } \ No newline at end of file diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 2e63aca..91e6cfd 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -191,26 +191,21 @@ function registerSTPACommands(manager: StpaLspVscodeExtension, context: vscode.E // commands for computing and displaying the (minimal) cut sets of the fault tree. context.subscriptions.push( vscode.commands.registerCommand(options.extensionPrefix + ".fta.cutSets", async (uri: vscode.Uri) => { - const cutSets: string[][] = await languageClient.sendRequest("generate/getCutSets", uri.path); + const cutSets: string[] = await languageClient.sendRequest("generate/getCutSets", uri.path); handleCutSets(manager, cutSets, false); }) ); context.subscriptions.push( vscode.commands.registerCommand(options.extensionPrefix + ".fta.minimalCutSets", async (uri: vscode.Uri) => { - const minimalCutSets: string[][] = await languageClient.sendRequest("generate/getMinimalCutSets", uri.path); + const minimalCutSets: string[] = await languageClient.sendRequest("generate/getMinimalCutSets", uri.path); handleCutSets(manager, minimalCutSets, true); }) ); } -function handleCutSets(manager: StpaLspVscodeExtension, cutSets: string[][], minimal?: boolean): void { - const cutSetStrings = cutSets.map((cutSet) => `[${cutSet.join(", ")}]`); +function handleCutSets(manager: StpaLspVscodeExtension, cutSets: string[], minimal?: boolean): void { // print cut sets to output channel - createOutputChannel(cutSetStrings, "FTA Cut Sets", minimal); - // send cut sets to the webview - manager.endpoints - .find((endpoint) => endpoint.diagramIdentifier?.diagramType === "fta") - ?.sendAction({ kind: SendCutSetAction.KIND, cutSets: cutSetStrings } as SendCutSetAction); + createOutputChannel(cutSets, "FTA Cut Sets", minimal); } function createLanguageClient(context: vscode.ExtensionContext): LanguageClient { From 24a347e84f9dc10d7bf48d998fa17e01f5e7ecb0 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Tue, 26 Sep 2023 12:32:02 +0200 Subject: [PATCH 50/61] highlighting of cut sets done in language server --- .../fta/diagram/fta-diagram-generator.ts | 23 ++++-- .../fta/diagram/fta-interfaces.ts | 5 +- .../fta/diagram/fta-model.ts | 1 + .../fta/fta-synthesis-options.ts | 21 +++-- extension/src-webview/di.config.ts | 5 +- extension/src-webview/fta-model.ts | 6 +- extension/src-webview/fta-views.tsx | 81 +++++++------------ 7 files changed, 63 insertions(+), 79 deletions(-) diff --git a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts index c3b0d7f..369d7a8 100644 --- a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts @@ -20,14 +20,17 @@ import { GeneratorContext, IdCache, LangiumDiagramGenerator } from "langium-spro import { SLabel, SModelElement, SModelRoot } from "sprotty-protocol"; import { ModelFTA, isComponent, isCondition, isKNGate } from "../../generated/ast"; import { FtaServices } from "../fta-module"; +import { FtaSynthesisOptions, noCutSet } from "../fta-synthesis-options"; import { namedFtaElement } from "../utils"; import { FTAEdge, FTANode } from "./fta-interfaces"; -import { FTA_EDGE_TYPE, FTA_NODE_TYPE } from "./fta-model"; +import { FTA_EDGE_TYPE, FTA_GRAPH_TYPE, FTA_NODE_TYPE } from "./fta-model"; import { getFTNodeType, getTargets } from "./utils"; export class FtaDiagramGenerator extends LangiumDiagramGenerator { + protected readonly options: FtaSynthesisOptions; constructor(services: FtaServices) { super(services); + this.options = services.options.SynthesisOptions; } /** @@ -52,7 +55,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { ]; return { - type: "graph", + type: FTA_GRAPH_TYPE, id: "root", children: ftaChildren, }; @@ -64,7 +67,7 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { * @param idCache The ID cache of the FTA model. * @returns edges representing the references the given {@code node} contains. */ - private generateEdges(node: AstNode, idCache: IdCache): SModelElement[] { + protected generateEdges(node: AstNode, idCache: IdCache): SModelElement[] { const elements: SModelElement[] = []; const sourceId = idCache.getId(node); // for every reference an edge is created @@ -89,21 +92,21 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { * @param label The label of the edge. * @returns an FTAEdge. */ - private generateFTEdge( + protected generateFTEdge( edgeId: string, sourceId: string, targetId: string, idCache: IdCache, label?: string ): FTAEdge { - const children = label ? this.createEdgeLabel(label, edgeId, idCache): []; + const children = label ? this.createEdgeLabel(label, edgeId, idCache) : []; return { type: FTA_EDGE_TYPE, id: edgeId, sourceId: sourceId, targetId: targetId, children: children, - highlight: true, + notConnectedToSelectedCutSet: false, }; } @@ -113,10 +116,13 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { * @param idCache The ID cache of the FTA model. * @returns a FTANode representing {@code node}. */ - private generateFTNode(node: namedFtaElement, idCache: IdCache): FTANode { + protected generateFTNode(node: namedFtaElement, idCache: IdCache): FTANode { const nodeId = idCache.uniqueId(node.name, node); const children: SModelElement[] = this.createNodeLabel(node.name, nodeId, idCache); const description = isComponent(node) || isCondition(node) ? node.description : ""; + const set = this.options.getCutSet(); + const includedInCutSet = set !== noCutSet.id ? set.includes(node.name) : false; + const notConnected = set !== noCutSet.id ? !includedInCutSet : false; const ftNode = { type: FTA_NODE_TYPE, @@ -125,8 +131,9 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { nodeType: getFTNodeType(node), description: description, children: children, - highlight: true, layout: "stack", + inCurrentSelectedCutSet: includedInCutSet, + notConnectedToSelectedCutSet: notConnected, layoutOptions: { paddingTop: 10.0, paddingBottom: 10.0, diff --git a/extension/src-language-server/fta/diagram/fta-interfaces.ts b/extension/src-language-server/fta/diagram/fta-interfaces.ts index 39492e5..42f2390 100644 --- a/extension/src-language-server/fta/diagram/fta-interfaces.ts +++ b/extension/src-language-server/fta/diagram/fta-interfaces.ts @@ -25,7 +25,8 @@ export interface FTANode extends SNode { name: string; nodeType: FTNodeType; description: string; - highlight?: boolean; + inCurrentSelectedCutSet?: boolean; + notConnectedToSelectedCutSet?: boolean; k?: number; n?: number; } @@ -34,5 +35,5 @@ export interface FTANode extends SNode { * Edge of a fault tree. */ export interface FTAEdge extends SEdge { - highlight?: boolean; + notConnectedToSelectedCutSet?: boolean; } diff --git a/extension/src-language-server/fta/diagram/fta-model.ts b/extension/src-language-server/fta/diagram/fta-model.ts index bff1a2a..0829f10 100644 --- a/extension/src-language-server/fta/diagram/fta-model.ts +++ b/extension/src-language-server/fta/diagram/fta-model.ts @@ -18,6 +18,7 @@ /* fault tree element types */ export const FTA_NODE_TYPE = "node:fta"; export const FTA_EDGE_TYPE = "edge:fta"; +export const FTA_GRAPH_TYPE = "graph:fta"; /** * Types of fault tree nodes. diff --git a/extension/src-language-server/fta/fta-synthesis-options.ts b/extension/src-language-server/fta/fta-synthesis-options.ts index 56be9b7..408a627 100644 --- a/extension/src-language-server/fta/fta-synthesis-options.ts +++ b/extension/src-language-server/fta/fta-synthesis-options.ts @@ -20,18 +20,20 @@ import { SynthesisOptions } from "../synthesis-options"; const cutSetsID = "cutSets"; +export const noCutSet = { displayName: "---", id: "---" }; + const cutSets: ValuedSynthesisOption = { synthesisOption: { id: cutSetsID, name: "Highlight Cut Set", type: TransformationOptionType.DROPDOWN, - currentId: "---", - availableValues: [{ displayName: "---", id: "noCutSet" }], - initialValue: "---", - currentValue: "---", + currentId: noCutSet.id, + availableValues: [noCutSet], + initialValue: noCutSet.id, + currentValue: noCutSet.id, values: [], } as DropDownOption, - currentValue: "---", + currentValue: noCutSet.id, }; export class FtaSynthesisOptions extends SynthesisOptions { @@ -47,10 +49,7 @@ export class FtaSynthesisOptions extends SynthesisOptions { updateCutSetsOption(values: { displayName: string; id: string }[]): void { const option = this.getOption(cutSetsID); if (option) { - (option.synthesisOption as DropDownOption).availableValues = [ - { displayName: "---", id: "noCutSet" }, - ...values, - ]; + (option.synthesisOption as DropDownOption).availableValues = [noCutSet, ...values]; // if the last selected cut set is not available anymore, // set the option to the first value of the new list if (!values.find((val) => val.id === (option.synthesisOption as DropDownOption).currentId)) { @@ -62,7 +61,7 @@ export class FtaSynthesisOptions extends SynthesisOptions { } } - setCutSets(value: string): void { + setCutSet(value: string): void { const option = this.options.find((option) => option.synthesisOption.id === cutSetsID); if (option) { option.currentValue = value; @@ -71,7 +70,7 @@ export class FtaSynthesisOptions extends SynthesisOptions { } } - getCutSets(): string { + getCutSet(): string { return this.getOption(cutSetsID)?.currentValue; } } diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts index 14fdfea..5ebedae 100644 --- a/extension/src-webview/di.config.ts +++ b/extension/src-webview/di.config.ts @@ -40,8 +40,8 @@ import { import { SvgCommand } from "./actions"; import { SvgPostprocessor } from "./exportPostProcessor"; import { CustomSvgExporter } from "./exporter"; -import { FTAEdge, FTANode, FTA_EDGE_TYPE, FTA_NODE_TYPE } from "./fta-model"; -import { FTANodeView, PolylineArrowEdgeViewFTA } from "./fta-views"; +import { FTAEdge, FTANode, FTA_EDGE_TYPE, FTA_GRAPH_TYPE, FTA_NODE_TYPE } from "./fta-model"; +import { FTAGraphView, FTANodeView, PolylineArrowEdgeViewFTA } from "./fta-views"; import { StpaModelViewer } from "./model-viewer"; import { optionsModule } from "./options/options-module"; import { sidebarModule } from "./sidebar"; @@ -106,6 +106,7 @@ const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => // FTA configureModelElement(context, FTA_EDGE_TYPE, FTAEdge, PolylineArrowEdgeViewFTA); configureModelElement(context, FTA_NODE_TYPE, FTANode, FTANodeView); + configureModelElement(context, FTA_GRAPH_TYPE, SGraph, FTAGraphView); }); export function createSTPADiagramContainer(widgetId: string): Container { diff --git a/extension/src-webview/fta-model.ts b/extension/src-webview/fta-model.ts index 2746ea2..5bb3ef2 100644 --- a/extension/src-webview/fta-model.ts +++ b/extension/src-webview/fta-model.ts @@ -29,6 +29,7 @@ import { /* fault tree element types */ export const FTA_NODE_TYPE = "node:fta"; export const FTA_EDGE_TYPE = "edge:fta"; +export const FTA_GRAPH_TYPE = "graph:fta"; /** * Node of a fault tree. @@ -46,7 +47,8 @@ export class FTANode extends SNode { name: string; nodeType: FTNodeType = FTNodeType.UNDEFINED; description: string = ""; - highlight?: boolean; + inCurrentSelectedCutSet?: boolean; + notConnectedToSelectedCutSet?: boolean; k?: number; n?: number; } @@ -55,7 +57,7 @@ export class FTANode extends SNode { * Edge of a fault tree. */ export class FTAEdge extends SEdge { - highlight?: boolean; + notConnectedToSelectedCutSet?: boolean; } /** diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta-views.tsx index b07850e..71f2c95 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta-views.tsx @@ -16,12 +16,10 @@ */ /** @jsx svg */ -import { inject, injectable } from 'inversify'; +import { injectable } from 'inversify'; import { VNode } from "snabbdom"; -import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, svg } from 'sprotty'; -import { DISymbol } from "./di.symbols"; +import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SGraph, SGraphView, svg } from 'sprotty'; import { FTAEdge, FTANode, FTNodeType } from './fta-model'; -import { CutSetsRegistry } from './options/cut-set-registry'; import { renderAndGate, renderCircle, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "./views-rendering"; // TODO: combine with STPA methods ?? @@ -37,8 +35,7 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { path += ` L ${p.x},${p.y}`; } // if an FTANode is selected, the components not connected to it should fade out - edge.highlight = (edge.target as FTANode).highlight; - return ; + return ; } } @@ -46,8 +43,6 @@ export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { @injectable() export class FTANodeView extends RectangularNodeView { - @inject(DISymbol.CutSetsRegistry) cutSetsRegistry: CutSetsRegistry; - render(node: FTANode, context: RenderingContext): VNode { // create the element based on the type of the node let element: VNode; @@ -78,60 +73,38 @@ export class FTANodeView extends RectangularNodeView { break; } - // if a cut set is selected, highlight the nodes in it and the connected elements - const set = this.cutSetsRegistry.getCurrentValue(); - let highlight = false; - if (set) { - switch (node.nodeType) { - case FTNodeType.COMPONENT: - case FTNodeType.CONDITION: - // components and conditions should be highlighted when included in the selected cut set - const included = set.includes(node.name); - node.highlight = included; - highlight = included; - break; - default: - // gates are hidden (greyed out) when not connected to shown elements - node.highlight = this.connectedToShown(node, set); - break; - } - } else { - node.highlight = true; - } - // TODO: replace highlight attribute with hidden + // if a cut set is selected, highlight the nodes in it and grey out not-connected elements return - {element} + class-greyed-out={node.notConnectedToSelectedCutSet}> + {element} {context.renderChildren(node)} ; } +} + +@injectable() +export class FTAGraphView extends SGraphView { + + render(model: Readonly, context: RenderingContext): VNode { + if (model.children.length !== 0) { + this.highlightConnectedToCutSet(model.children[0] as FTANode); + } + + return super.render(model, context); + } - /** - * Checks whether the given {@code node} is connected to a highlighted node. - * @param node The node that should be checked. - * @param set The set of all highlighted nodes. - * @returns true if the node is connected to a node from the {@code set} or false otherwise. - */ - connectedToShown(node: FTANode, set: any): boolean { - // TODO: call this method only one time at the top node and highlight everything on the way that should be highlighted + protected highlightConnectedToCutSet(node: FTANode): void { for (const edge of node.outgoingEdges) { - const target = (edge.target as FTANode); - switch (target.nodeType) { - case FTNodeType.COMPONENT: - case FTNodeType.CONDITION: - if (set.includes(target.name)) { - return true; - } - break; - default: - if (this.connectedToShown(target, set)) { - return true; - } - break; + (edge as FTAEdge).notConnectedToSelectedCutSet = true; + const target = edge.target as FTANode; + this.highlightConnectedToCutSet(target); + if (!target.notConnectedToSelectedCutSet) { + node.notConnectedToSelectedCutSet = false; + (edge as FTAEdge).notConnectedToSelectedCutSet = false; } } - return false; } -} + +} \ No newline at end of file From 12c61b04b660d8ba01b03a9969a08e1578efe4c1 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Tue, 26 Sep 2023 12:35:26 +0200 Subject: [PATCH 51/61] deleted cut set panel --- extension/src-webview/di.symbols.ts | 1 - extension/src-webview/options/actions.ts | 23 ------ .../src-webview/options/cut-set-panel.tsx | 64 --------------- .../src-webview/options/cut-set-registry.ts | 81 ------------------- .../src-webview/options/options-module.ts | 9 +-- extension/src/actions.ts | 24 +----- extension/src/extension.ts | 1 - 7 files changed, 2 insertions(+), 201 deletions(-) delete mode 100644 extension/src-webview/options/cut-set-panel.tsx delete mode 100644 extension/src-webview/options/cut-set-registry.ts diff --git a/extension/src-webview/di.symbols.ts b/extension/src-webview/di.symbols.ts index 3d36d19..e209b17 100644 --- a/extension/src-webview/di.symbols.ts +++ b/extension/src-webview/di.symbols.ts @@ -24,5 +24,4 @@ export const DISymbol = { OptionsRenderer: Symbol("OptionsRenderer"), OptionsRegistry: Symbol("OptionsRegistry"), RenderOptionsRegistry: Symbol("RenderOptionsRegistry"), - CutSetsRegistry: Symbol("CutSetsRegistry"), }; diff --git a/extension/src-webview/options/actions.ts b/extension/src-webview/options/actions.ts index 37e3575..d5b639d 100644 --- a/extension/src-webview/options/actions.ts +++ b/extension/src-webview/options/actions.ts @@ -128,26 +128,3 @@ export namespace SendConfigAction { return action.kind === SendConfigAction.KIND; } } - -/** Contains the cut sets for a fault tree */ -export interface SendCutSetAction extends Action { - kind: typeof SendCutSetAction.KIND; - cutSets: string[]; -} - - export namespace SendCutSetAction { - export const KIND = "sendCutSets"; - - export function create(cutSets: string[]): SendCutSetAction { - return { - kind: KIND, - cutSets - }; - } - - export function isThisAction(action: Action): action is SendCutSetAction { - return action.kind === SendCutSetAction.KIND; - } -} - - diff --git a/extension/src-webview/options/cut-set-panel.tsx b/extension/src-webview/options/cut-set-panel.tsx deleted file mode 100644 index 2982e54..0000000 --- a/extension/src-webview/options/cut-set-panel.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient - * - * http://rtsys.informatik.uni-kiel.de/kieler - * - * Copyright 2023 by - * + Kiel University - * + Department of Computer Science - * + Real-Time and Embedded Systems Group - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - */ - -/** @jsx html */ -import { inject, injectable, postConstruct } from "inversify"; -import { VNode } from "snabbdom"; -import { html } from "sprotty"; -import { DISymbol } from "../di.symbols"; -import { FeatherIcon } from '../feather-icons-snabbdom/feather-icons-snabbdom'; -import { SidebarPanel } from "../sidebar"; -import { CutSetsRegistry } from "./cut-set-registry"; -import { OptionsRenderer } from "./options-renderer"; - -// TODO: extra panel needed? should not be shown for stpa diagrams -@injectable() -export class CutSetPanel extends SidebarPanel { - - @inject(DISymbol.CutSetsRegistry) private cutSetsRegistry: CutSetsRegistry; - @inject(DISymbol.OptionsRenderer) private optionsRenderer: OptionsRenderer; - - @postConstruct() - init(): void { - this.cutSetsRegistry.onChange(() => this.update()); - } - get id(): string { - return "cut-set-panel"; - } - - get title(): string { - return "Cut Sets"; - } - - render(): VNode { - return ( -
-
-
Cut Sets
- {this.optionsRenderer.renderRenderOptions( - this.cutSetsRegistry.allCutSets - )} -
-
- ); - } - - // TODO: other icon? - get icon(): VNode { - return ; - } -} \ No newline at end of file diff --git a/extension/src-webview/options/cut-set-registry.ts b/extension/src-webview/options/cut-set-registry.ts deleted file mode 100644 index 7ab6106..0000000 --- a/extension/src-webview/options/cut-set-registry.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient - * - * http://rtsys.informatik.uni-kiel.de/kieler - * - * Copyright 2023 by - * + Kiel University - * + Department of Computer Science - * + Real-Time and Embedded Systems Group - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0. - * - * SPDX-License-Identifier: EPL-2.0 - */ - -import { injectable } from "inversify"; -import { ICommand } from "sprotty"; -import { Action, UpdateModelAction } from "sprotty-protocol"; -import { Registry } from "../base/registry"; -import { SendCutSetAction } from "./actions"; -import { DropDownOption, TransformationOptionType } from "./option-models"; - - -const noSelectedCutSet = { displayName: "---", id: "---" }; - -// TODO: should be a synthesis option ?? -export class DropDownMenuOption implements DropDownOption { - static readonly ID = "cut-sets"; - static readonly NAME = "Cut Sets"; - readonly id = DropDownMenuOption.ID; - readonly currentId = DropDownMenuOption.ID; - readonly name = DropDownMenuOption.NAME; - readonly type = TransformationOptionType.DROPDOWN; - values = [noSelectedCutSet]; - availableValues = [noSelectedCutSet]; - readonly initialValue = noSelectedCutSet; - currentValue = noSelectedCutSet; -} - -/** {@link Registry} that stores and updates cut set options. */ -@injectable() -export class CutSetsRegistry extends Registry { - private _options: Map = new Map(); - - handle(action: Action): void | Action | ICommand { - if (SendCutSetAction.isThisAction(action)) { - const dropDownOption = new DropDownMenuOption(); - for (const set of action.cutSets) { - dropDownOption.availableValues.push({ displayName: set, id: set }); - } - - this._options.set("cut-sets", dropDownOption); - this.notifyListeners(); - } - return UpdateModelAction.create([], { animate: false, cause: action }); - } - - get allCutSets(): DropDownOption[] { - return Array.from(this._options.values()); - } - - getCurrentValue(): string[] | undefined { - // if the cut sets were not requested yet, there are no cut sets to display - if (this._options.get("cut-sets")?.availableValues.length === 1) { - return undefined; - } - const selectedCutSet: { displayName: string; id: string } = this._options.get("cut-sets")?.currentValue; - if (selectedCutSet) { - // slice the brackets at the start and at the end. - const selected = selectedCutSet.displayName.slice(1, -1); - if (selected === "-") { - return undefined; - } else { - // determine the names in the cut set - return selected.split(",").map(element => element.trim()); - } - } - } -} diff --git a/extension/src-webview/options/options-module.ts b/extension/src-webview/options/options-module.ts index e9d94dd..2174e6c 100644 --- a/extension/src-webview/options/options-module.ts +++ b/extension/src-webview/options/options-module.ts @@ -18,9 +18,7 @@ import { ContainerModule } from "inversify"; import { configureActionHandler, TYPES } from "sprotty"; import { DISymbol } from "../di.symbols"; -import { ResetRenderOptionsAction, SendConfigAction, SendCutSetAction, SetRenderOptionAction } from "./actions"; -import { CutSetPanel } from "./cut-set-panel"; -import { CutSetsRegistry } from "./cut-set-registry"; +import { ResetRenderOptionsAction, SendConfigAction, SetRenderOptionAction } from "./actions"; import { GeneralPanel } from "./general-panel"; import { OptionsPanel } from "./options-panel"; import { OptionsRegistry } from "./options-registry"; @@ -35,19 +33,14 @@ export const optionsModule = new ContainerModule((bind, _, isBound) => { bind(OptionsPanel).toSelf().inSingletonScope(); bind(DISymbol.SidebarPanel).toService(OptionsPanel); - bind(CutSetPanel).toSelf().inSingletonScope(); - bind(DISymbol.SidebarPanel).toService(CutSetPanel); - bind(DISymbol.OptionsRenderer).to(OptionsRenderer); bind(DISymbol.OptionsRegistry).to(OptionsRegistry).inSingletonScope(); bind(TYPES.IActionHandlerInitializer).toService(DISymbol.OptionsRegistry); bind(DISymbol.RenderOptionsRegistry).to(RenderOptionsRegistry).inSingletonScope(); - bind(DISymbol.CutSetsRegistry).to(CutSetsRegistry).inSingletonScope(); const ctx = { bind, isBound }; configureActionHandler(ctx, SetRenderOptionAction.KIND, DISymbol.RenderOptionsRegistry); configureActionHandler(ctx, ResetRenderOptionsAction.KIND, DISymbol.RenderOptionsRegistry); configureActionHandler(ctx, SendConfigAction.KIND, DISymbol.RenderOptionsRegistry); - configureActionHandler(ctx, SendCutSetAction.KIND, DISymbol.CutSetsRegistry); }); diff --git a/extension/src/actions.ts b/extension/src/actions.ts index 24713be..11710c6 100644 --- a/extension/src/actions.ts +++ b/extension/src/actions.ts @@ -59,26 +59,4 @@ export namespace GenerateSVGsAction { export function isThisAction(action: Action): action is GenerateSVGsAction { return action.kind === GenerateSVGsAction.KIND; } -} - -// TODO: better type for cut sets? -/** Contains the cut sets for a fault tree */ -export interface SendCutSetAction extends Action { - kind: typeof SendCutSetAction.KIND; - cutSets: string[]; -} - - export namespace SendCutSetAction { - export const KIND = "sendCutSets"; - - export function create(cutSets: string[]): SendCutSetAction { - return { - kind: KIND, - cutSets - }; - } - - export function isThisAction(action: Action): action is SendCutSetAction { - return action.kind === SendCutSetAction.KIND; - } -} +} \ No newline at end of file diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 91e6cfd..d7530a4 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -21,7 +21,6 @@ import { LspSprottyEditorProvider, LspSprottyViewProvider } from 'sprotty-vscode import * as vscode from 'vscode'; import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node'; import { Messenger } from 'vscode-messenger'; -import { SendCutSetAction } from './actions'; import { command } from './constants'; import { StpaLspVscodeExtension } from './language-extension'; import { createSTPAResultMarkdownFile } from './report/md-export'; From 8bb4427d16eac6b41ff466506b876fff953ce971 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Tue, 26 Sep 2023 12:45:49 +0200 Subject: [PATCH 52/61] refactored message handler --- .../fta/fta-message-handler.ts | 53 ++++++++++++------- .../fta/fta-synthesis-options.ts | 11 ++-- 2 files changed, 39 insertions(+), 25 deletions(-) diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index e251e22..c736d6c 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -26,6 +26,7 @@ import { cutSetsToString } from "./utils"; * Adds handlers for notifications regarding fta. * @param connection * @param ftaServices + * @param sharedServices */ export function addFTANotificationHandler( connection: Connection, @@ -39,28 +40,42 @@ export function addFTANotificationHandler( * Adds handlers for requests regarding the cut sets. * @param connection * @param ftaServices + * @param sharedServices */ -function addCutSetsHandler(connection: Connection, ftaServices: FtaServices, sharedServices: LangiumSprottySharedServices): void { +function addCutSetsHandler( + connection: Connection, + ftaServices: FtaServices, + sharedServices: LangiumSprottySharedServices +): void { connection.onRequest("generate/getCutSets", async (uri: string) => { - const model = await getFTAModel(uri, sharedServices); - const nodes = [model.topEvent, ...model.components, ...model.conditions, ...model.gates]; - const cutSets = determineCutSetsForFT(nodes); - const cutSetsString = cutSetsToString(cutSets); - const dropdownValues = cutSetsString.map((cutSet) => { - return { displayName: cutSet, id: cutSet }; - }); - ftaServices.options.SynthesisOptions.updateCutSetsOption(dropdownValues); - return cutSetsString; + return cutSetsRequested(uri, ftaServices, sharedServices, false); }); connection.onRequest("generate/getMinimalCutSets", async (uri: string) => { - const model = await getFTAModel(uri, sharedServices); - const nodes = [model.topEvent, ...model.components, ...model.conditions, ...model.gates]; - const minimalCutSets = determineMinimalCutSets(nodes); - const cutSetsString = cutSetsToString(minimalCutSets); - const dropdownValues = cutSetsString.map((cutSet) => { - return { displayName: cutSet, id: cutSet }; - }); - ftaServices.options.SynthesisOptions.updateCutSetsOption(dropdownValues); - return cutSetsString; + return cutSetsRequested(uri, ftaServices, sharedServices, true); + }); +} + +/** + * Determines the (minimal) cut sets and return them as a list of strings. + * @param uri The uri of the model for which the cut sets should be determined. + * @param ftaServices + * @param sharedServices + * @param minimal Determines whether all cut sets or only the minimal cut sets should be determined. + * @returns the (minimal) cut sets of the model given by {@code uri} as list of strings. + */ +async function cutSetsRequested( + uri: string, + ftaServices: FtaServices, + sharedServices: LangiumSprottySharedServices, + minimal: boolean +): Promise { + const model = await getFTAModel(uri, sharedServices); + const nodes = [model.topEvent, ...model.components, ...model.conditions, ...model.gates]; + const cutSets = minimal ? determineMinimalCutSets(nodes) : determineCutSetsForFT(nodes); + const cutSetsString = cutSetsToString(cutSets); + const dropdownValues = cutSetsString.map((cutSet) => { + return { displayName: cutSet, id: cutSet }; }); + ftaServices.options.SynthesisOptions.updateCutSetsOption(dropdownValues); + return cutSetsString; } diff --git a/extension/src-language-server/fta/fta-synthesis-options.ts b/extension/src-language-server/fta/fta-synthesis-options.ts index 408a627..bb85b06 100644 --- a/extension/src-language-server/fta/fta-synthesis-options.ts +++ b/extension/src-language-server/fta/fta-synthesis-options.ts @@ -50,13 +50,12 @@ export class FtaSynthesisOptions extends SynthesisOptions { const option = this.getOption(cutSetsID); if (option) { (option.synthesisOption as DropDownOption).availableValues = [noCutSet, ...values]; - // if the last selected cut set is not available anymore, - // set the option to the first value of the new list + // if the last selected cut set is not available anymore, set the option to no cut set if (!values.find((val) => val.id === (option.synthesisOption as DropDownOption).currentId)) { - (option.synthesisOption as DropDownOption).currentId = values[0].id; - option.synthesisOption.currentValue = values[0].id; - option.synthesisOption.initialValue = values[0].id; - option.currentValue = values[0].id; + (option.synthesisOption as DropDownOption).currentId = noCutSet.id; + option.synthesisOption.currentValue = noCutSet.id; + option.synthesisOption.initialValue = noCutSet.id; + option.currentValue = noCutSet.id; } } } From 2f656ea25772df5b5bf5829408134d9409e9cbcd Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Tue, 26 Sep 2023 13:02:35 +0200 Subject: [PATCH 53/61] automatic update when generating cut sets --- extension/src/extension.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extension/src/extension.ts b/extension/src/extension.ts index d7530a4..3c80375 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -191,12 +191,14 @@ function registerSTPACommands(manager: StpaLspVscodeExtension, context: vscode.E context.subscriptions.push( vscode.commands.registerCommand(options.extensionPrefix + ".fta.cutSets", async (uri: vscode.Uri) => { const cutSets: string[] = await languageClient.sendRequest("generate/getCutSets", uri.path); + await manager.openDiagram(uri); handleCutSets(manager, cutSets, false); }) ); context.subscriptions.push( vscode.commands.registerCommand(options.extensionPrefix + ".fta.minimalCutSets", async (uri: vscode.Uri) => { const minimalCutSets: string[] = await languageClient.sendRequest("generate/getMinimalCutSets", uri.path); + await manager.openDiagram(uri); handleCutSets(manager, minimalCutSets, true); }) ); From dd9756f29ad3d958c6643673221c8fff834b823f Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Tue, 26 Sep 2023 13:23:50 +0200 Subject: [PATCH 54/61] merged getModel functions --- .../src-language-server/diagram-server.ts | 3 -- .../fta/fta-message-handler.ts | 5 ++- extension/src-language-server/handler.ts | 4 +- .../stpa/contextTable/context-dataProvider.ts | 4 +- .../stpa/diagram/layout-config.ts | 1 - .../stpa/modelChecking/model-checking.ts | 6 +-- .../stpa/result-report/result-generator.ts | 5 ++- extension/src-language-server/stpa/utils.ts | 5 ++- extension/src-language-server/utils.ts | 37 +++++-------------- extension/src/report/md-export.ts | 1 - extension/src/wview.ts | 1 - 11 files changed, 25 insertions(+), 47 deletions(-) diff --git a/extension/src-language-server/diagram-server.ts b/extension/src-language-server/diagram-server.ts index 5713610..7c22b01 100644 --- a/extension/src-language-server/diagram-server.ts +++ b/extension/src-language-server/diagram-server.ts @@ -89,9 +89,6 @@ export class PastaDiagramServer extends DiagramServer { return this.handleSetSynthesisOption(action as SetSynthesisOptionsAction); case GenerateSVGsAction.KIND: return this.handleGenerateSVGDiagrams(action as GenerateSVGsAction); - // TODO: implement selection of an element here instead of in "handler" and "wview" ?? - // case SelectAction.KIND: - // return this.handleSelectaction } return super.handleAction(action); } diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index c736d6c..b10cc3b 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -17,10 +17,11 @@ import { LangiumSprottySharedServices } from "langium-sprotty"; import { Connection } from "vscode-languageserver"; -import { getFTAModel } from "../utils"; +import { getModel } from "../utils"; import { determineMinimalCutSets, determineCutSetsForFT } from "./analysis/fta-cutSet-calculator"; import { FtaServices } from "./fta-module"; import { cutSetsToString } from "./utils"; +import { ModelFTA } from "../generated/ast"; /** * Adds handlers for notifications regarding fta. @@ -69,7 +70,7 @@ async function cutSetsRequested( sharedServices: LangiumSprottySharedServices, minimal: boolean ): Promise { - const model = await getFTAModel(uri, sharedServices); + const model = (await getModel(uri, sharedServices)) as ModelFTA; const nodes = [model.topEvent, ...model.components, ...model.conditions, ...model.gates]; const cutSets = minimal ? determineMinimalCutSets(nodes) : determineCutSetsForFT(nodes); const cutSetsString = cutSetsToString(cutSets); diff --git a/extension/src-language-server/handler.ts b/extension/src-language-server/handler.ts index 0cb0e4c..5021c56 100644 --- a/extension/src-language-server/handler.ts +++ b/extension/src-language-server/handler.ts @@ -18,7 +18,7 @@ import { LangiumSprottySharedServices } from "langium-sprotty"; import { Model } from "./generated/ast"; import { Connection, Range } from "vscode-languageserver"; -import { getSTPAModel } from "./utils"; +import { getModel } from "./utils"; import { elementWithName } from "./stpa/utils"; /** @@ -32,7 +32,7 @@ export function addNotificationHandler(connection: Connection, shared: LangiumSp // TODO: implement for FTA if (msg.uri.endsWith(".stpa")) { // get the current model - const model = await getSTPAModel(msg.uri, shared); + const model = await getModel(msg.uri, shared) as Model; // determine the range in the editor of the component identified by "label" const range = getRangeOfNode(model, msg.label); diff --git a/extension/src-language-server/stpa/contextTable/context-dataProvider.ts b/extension/src-language-server/stpa/contextTable/context-dataProvider.ts index cf60016..b072c56 100644 --- a/extension/src-language-server/stpa/contextTable/context-dataProvider.ts +++ b/extension/src-language-server/stpa/contextTable/context-dataProvider.ts @@ -26,7 +26,7 @@ import { ContextTableVariableValues, } from "../../../src-context-table/utils"; import { Model } from "../../generated/ast"; -import { getSTPAModel } from "../../utils"; +import { getModel } from "../../utils"; import { StpaServices } from "../stpa-module"; export class ContextTableProvider { @@ -66,7 +66,7 @@ export class ContextTableProvider { */ async getData(uri: URI): Promise { // get the current model - const model = await getSTPAModel(uri, this.services.shared); + const model = await getModel(uri, this.services.shared) as Model; const actions: ContextTableControlAction[] = []; const variables: ContextTableSystemVariables[] = []; diff --git a/extension/src-language-server/stpa/diagram/layout-config.ts b/extension/src-language-server/stpa/diagram/layout-config.ts index d2c4ce3..f0d61bb 100644 --- a/extension/src-language-server/stpa/diagram/layout-config.ts +++ b/extension/src-language-server/stpa/diagram/layout-config.ts @@ -74,7 +74,6 @@ export class StpaLayoutConfigurator extends DefaultLayoutConfigurator { } protected nodeOptions(snode: SNode, index: SModelIndex): LayoutOptions | undefined { - // TODO: check whether model order option is activated switch (snode.type) { case CS_NODE_TYPE: return this.csNodeOptions(snode as CSNode); diff --git a/extension/src-language-server/stpa/modelChecking/model-checking.ts b/extension/src-language-server/stpa/modelChecking/model-checking.ts index e4e6a1c..c8f89b8 100644 --- a/extension/src-language-server/stpa/modelChecking/model-checking.ts +++ b/extension/src-language-server/stpa/modelChecking/model-checking.ts @@ -19,7 +19,7 @@ import { Reference } from "langium"; import { LangiumSprottySharedServices } from "langium-sprotty"; import { URI } from "vscode-uri"; import { DCARule, Model, Rule, Variable, VariableValue, isRule } from "../../generated/ast"; -import { getSTPAModel } from "../../utils"; +import { getModel } from "../../utils"; /** * Respresents an LTL formula. @@ -71,14 +71,14 @@ export async function generateLTLFormulae( shared: LangiumSprottySharedServices ): Promise> { // get the current model - let model = await getSTPAModel(uri, shared); + let model = await getModel(uri, shared) as Model; // references are not found if the stpa file has not been opened since then the linter has not been activated yet if (model.rules.length > 0 && model.rules[0]?.contexts[0]?.vars[0]?.ref === undefined) { // build document await shared.workspace.DocumentBuilder.update([URI.parse(uri)], []); // update the model - model = await getSTPAModel(uri, shared); + model = await getModel(uri, shared) as Model; } // ltl formulas are saved per controller const map: Record = {}; diff --git a/extension/src-language-server/stpa/result-report/result-generator.ts b/extension/src-language-server/stpa/result-report/result-generator.ts index b3834f1..af546e0 100644 --- a/extension/src-language-server/stpa/result-report/result-generator.ts +++ b/extension/src-language-server/stpa/result-report/result-generator.ts @@ -22,13 +22,14 @@ import { ControllerConstraint, Hazard, LossScenario, + Model, Responsibility, Rule, SafetyConstraint, SystemConstraint, UCA } from "../../generated/ast"; -import { getSTPAModel } from "../../utils"; +import { getModel } from "../../utils"; import { StpaComponent, StpaResult, UCA_TYPE } from "../utils"; /** @@ -40,7 +41,7 @@ import { StpaComponent, StpaResult, UCA_TYPE } from "../utils"; export async function createResultData(uri: string, shared: LangiumSprottySharedServices): Promise { const result: StpaResult = new StpaResult(); // get the current model - const model = await getSTPAModel(uri, shared); + const model = await getModel(uri, shared) as Model; // losses const resultLosses: { id: string; description: string }[] = []; diff --git a/extension/src-language-server/stpa/utils.ts b/extension/src-language-server/stpa/utils.ts index bf0e4d0..2478d90 100644 --- a/extension/src-language-server/stpa/utils.ts +++ b/extension/src-language-server/stpa/utils.ts @@ -33,8 +33,9 @@ import { SystemConstraint, UCA, Variable, + Model, } from "../generated/ast"; -import { getSTPAModel } from "../utils"; +import { getModel } from "../utils"; export type leafElement = | Loss @@ -81,7 +82,7 @@ export async function getControlActions( ): Promise> { const controlActionsMap: Record = {}; // get the model from the file determined by the uri - const model = await getSTPAModel(uri, shared); + const model = await getModel(uri, shared) as Model; // collect control actions grouped by their controller model.controlStructure?.nodes.forEach((systemComponent) => { systemComponent.actions.forEach((action) => { diff --git a/extension/src-language-server/utils.ts b/extension/src-language-server/utils.ts index 1d4c64c..69fcc52 100644 --- a/extension/src-language-server/utils.ts +++ b/extension/src-language-server/utils.ts @@ -15,40 +15,21 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { LangiumDocument, LangiumSharedServices } from "langium"; +import { AstNode, LangiumSharedServices } from "langium"; import { LangiumSprottySharedServices } from "langium-sprotty"; -import { URI } from 'vscode-uri'; -import { Model, ModelFTA } from './generated/ast'; - -// TODO: merge the functions into one? - -/** - * Determines the model for {@code uri}. - * @param uri The URI for which the model is desired. - * @param shared The shared service. - * @returns the model for the given uri. - */ -export async function getSTPAModel(uri: string, shared: LangiumSprottySharedServices | LangiumSharedServices): Promise { - const textDocuments = shared.workspace.LangiumDocuments; - const currentDoc = textDocuments.getOrCreateDocument(URI.parse(uri)) as LangiumDocument; - let currentModel = currentDoc.parseResult.value; - if (currentModel.rules.length !== 0 && currentModel.rules[0]?.contexts[0]?.vars[0]?.ref === undefined) { - // build document - await shared.workspace.DocumentBuilder.update([URI.parse(uri)], []); - // update the model - currentModel = currentDoc.parseResult.value; - } - return currentModel; -} +import { URI } from "vscode-uri"; /** * Determines the model for {@code uri}. * @param uri The URI for which the model is desired. - * @param shared The shared service. + * @param shared The shared services. * @returns the model for the given uri. */ -export async function getFTAModel(uri: string, shared: LangiumSprottySharedServices | LangiumSharedServices): Promise { +export async function getModel( + uri: string, + shared: LangiumSprottySharedServices | LangiumSharedServices +): Promise { const textDocuments = shared.workspace.LangiumDocuments; - const currentDoc = textDocuments.getOrCreateDocument(URI.parse(uri)) as LangiumDocument; + const currentDoc = textDocuments.getOrCreateDocument(URI.parse(uri)); return currentDoc.parseResult.value; -} \ No newline at end of file +} diff --git a/extension/src/report/md-export.ts b/extension/src/report/md-export.ts index 90fa147..a02a32a 100644 --- a/extension/src/report/md-export.ts +++ b/extension/src/report/md-export.ts @@ -80,7 +80,6 @@ export async function createSTPAResultMarkdownFile(data: StpaResult, extension: * @returns markdown text for the given {@code data} */ function createSTPAResultMarkdownText(data: StpaResult, diagramSizes: Record): string { - // TODO: consider context table let markdown = `# STPA Report for ${data.title}\n\n`; // table of contents markdown += createTOC(); diff --git a/extension/src/wview.ts b/extension/src/wview.ts index 117eb75..57b54b2 100644 --- a/extension/src/wview.ts +++ b/extension/src/wview.ts @@ -22,7 +22,6 @@ import { SendConfigAction } from "./actions"; export class StpaLspWebview extends LspWebviewEndpoint { receiveAction(message: any): Promise { - // TODO: for multiple language support here the current language muste be determined if (isActionMessage(message)) { switch (message.action.kind) { case "optionRegistryReadyMessage": From 766e7ff6e229dbee3a70b9c361a9e0900804e833 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Tue, 26 Sep 2023 13:32:08 +0200 Subject: [PATCH 55/61] node selection highlights textual definition --- extension/src-language-server/fta/utils.ts | 26 ++++++- extension/src-language-server/handler.ts | 78 ++++++--------------- extension/src-language-server/stpa/utils.ts | 40 ++++++++++- 3 files changed, 86 insertions(+), 58 deletions(-) diff --git a/extension/src-language-server/fta/utils.ts b/extension/src-language-server/fta/utils.ts index 379657f..938a680 100644 --- a/extension/src-language-server/fta/utils.ts +++ b/extension/src-language-server/fta/utils.ts @@ -15,7 +15,8 @@ * SPDX-License-Identifier: EPL-2.0 */ -import { Component, Condition, Gate, TopEvent } from "../generated/ast"; +import { Range } from "vscode-languageserver"; +import { Component, Condition, Gate, ModelFTA, TopEvent } from "../generated/ast"; export type namedFtaElement = Component | Condition | Gate | TopEvent; @@ -33,3 +34,26 @@ export function cutSetsToString(cutSets: Set[]): string[] { } return result; } + +/** + * Determines the range of the component identified by {@code label} in the editor, + * @param model The current STPA model. + * @param label The label of the searched component. + * @returns The range of the component idenified by the label or undefined if no component was found. + */ +export function getRangeOfNodeFTA(model: ModelFTA, label: string): Range | undefined { + let range: Range | undefined = undefined; + const elements: namedFtaElement[] = [ + model.topEvent, + ...model.components, + ...model.conditions, + ...model.gates + ]; + elements.forEach((component) => { + if (component.name === label) { + range = component.$cstNode?.range; + return; + } + }); + return range; +} \ No newline at end of file diff --git a/extension/src-language-server/handler.ts b/extension/src-language-server/handler.ts index 5021c56..0012cc9 100644 --- a/extension/src-language-server/handler.ts +++ b/extension/src-language-server/handler.ts @@ -16,10 +16,11 @@ */ import { LangiumSprottySharedServices } from "langium-sprotty"; -import { Model } from "./generated/ast"; import { Connection, Range } from "vscode-languageserver"; +import { Model, isModel, isModelFTA } from "./generated/ast"; +import { getRangeOfNodeSTPA } from "./stpa/utils"; import { getModel } from "./utils"; -import { elementWithName } from "./stpa/utils"; +import { getRangeOfNodeFTA } from "./fta/utils"; /** * Adds handler for notifications. @@ -27,62 +28,29 @@ import { elementWithName } from "./stpa/utils"; * @param shared Shared services containing the workspace. */ export function addNotificationHandler(connection: Connection, shared: LangiumSprottySharedServices): void { - // diagram + // node selection in diagram connection.onNotification("diagram/selected", async (msg: { label: string; uri: string }) => { - // TODO: implement for FTA - if (msg.uri.endsWith(".stpa")) { - // get the current model - const model = await getModel(msg.uri, shared) as Model; + // get the current model + const model = (await getModel(msg.uri, shared)) as Model; - // determine the range in the editor of the component identified by "label" - const range = getRangeOfNode(model, msg.label); - if (range) { - // notify extension to highlight the range in the editor - connection.sendNotification("editor/highlight", { - startLine: range.start.line, - startChar: range.start.character, - endLine: range.end.line, - endChar: range.end.character, - uri: msg.uri, - }); - } else { - console.log("The selected element could not be found in the editor."); - } + let range: Range | undefined = undefined; + // determine the range in the editor of the component identified by "label" + if (isModel(model)) { + range = getRangeOfNodeSTPA(model, msg.label); + } else if (isModelFTA(model)) { + range = getRangeOfNodeFTA(model, msg.label); } - }); -} - -/** - * Determines the range of the component identified by {@code label} in the editor, - * @param model The current STPA model. - * @param label The label of the searched component. - * @returns The range of the component idenified by the label or undefined if no component was found. - */ -function getRangeOfNode(model: Model, label: string): Range | undefined { - let range: Range | undefined = undefined; - const elements: elementWithName[] = [ - ...model.losses, - ...model.hazards, - ...model.hazards.flatMap((hazard) => hazard.subComponents), - ...model.systemLevelConstraints, - ...model.systemLevelConstraints.flatMap((constraint) => constraint.subComponents), - ...model.responsibilities.flatMap((resp) => resp.responsiblitiesForOneSystem), - ...model.allUCAs.flatMap((ucas) => - ucas.providingUcas.concat(ucas.notProvidingUcas, ucas.wrongTimingUcas, ucas.continousUcas) - ), - ...model.rules.flatMap((rule) => rule.contexts), - ...model.controllerConstraints, - ...model.scenarios, - ...model.safetyCons, - ]; - if (model.controlStructure) { - elements.push(...model.controlStructure.nodes); - } - elements.forEach((component) => { - if (component.name === label) { - range = component.$cstNode?.range; - return; + if (range) { + // notify extension to highlight the range in the editor + connection.sendNotification("editor/highlight", { + startLine: range.start.line, + startChar: range.start.character, + endLine: range.end.line, + endChar: range.end.character, + uri: msg.uri, + }); + } else { + console.log("The selected element could not be found in the editor."); } }); - return range; } diff --git a/extension/src-language-server/stpa/utils.ts b/extension/src-language-server/stpa/utils.ts index 2478d90..e79d4da 100644 --- a/extension/src-language-server/stpa/utils.ts +++ b/extension/src-language-server/stpa/utils.ts @@ -17,15 +17,17 @@ import { LangiumSharedServices } from "langium"; import { LangiumSprottySharedServices } from "langium-sprotty"; +import { Range } from "vscode-languageserver"; import { Command, - ControllerConstraint, Context, + ControllerConstraint, Graph, Hazard, HazardList, Loss, LossScenario, + Model, Node, Responsibility, Rule, @@ -33,7 +35,6 @@ import { SystemConstraint, UCA, Variable, - Model, } from "../generated/ast"; import { getModel } from "../utils"; @@ -155,3 +156,38 @@ export class UCA_TYPE { static CONTINUOUS = "continuous-problem"; static UNDEFINED = "undefined"; } + +/** + * Determines the range of the component identified by {@code label} in the editor, + * @param model The current STPA model. + * @param label The label of the searched component. + * @returns The range of the component idenified by the label or undefined if no component was found. + */ +export function getRangeOfNodeSTPA(model: Model, label: string): Range | undefined { + let range: Range | undefined = undefined; + const elements: elementWithName[] = [ + ...model.losses, + ...model.hazards, + ...model.hazards.flatMap((hazard) => hazard.subComponents), + ...model.systemLevelConstraints, + ...model.systemLevelConstraints.flatMap((constraint) => constraint.subComponents), + ...model.responsibilities.flatMap((resp) => resp.responsiblitiesForOneSystem), + ...model.allUCAs.flatMap((ucas) => + ucas.providingUcas.concat(ucas.notProvidingUcas, ucas.wrongTimingUcas, ucas.continousUcas) + ), + ...model.rules.flatMap((rule) => rule.contexts), + ...model.controllerConstraints, + ...model.scenarios, + ...model.safetyCons, + ]; + if (model.controlStructure) { + elements.push(...model.controlStructure.nodes); + } + elements.forEach((component) => { + if (component.name === label) { + range = component.$cstNode?.range; + return; + } + }); + return range; +} \ No newline at end of file From 95700ff8aa689ab9fa13ff3b8ea9f2d6b7f174ef Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Tue, 26 Sep 2023 13:38:53 +0200 Subject: [PATCH 56/61] organizing --- extension/src-webview/di.config.ts | 14 +++++++------- extension/src-webview/diagram-server.ts | 2 +- extension/src-webview/{ => fta}/fta-model.ts | 0 extension/src-webview/{ => fta}/fta-views.tsx | 4 +--- extension/src-webview/main.ts | 4 ++-- extension/src-webview/model-viewer.ts | 2 +- extension/src-webview/{ => stpa}/helper-methods.ts | 0 extension/src-webview/{ => stpa}/stpa-model.ts | 0 .../src-webview/{ => stpa}/stpa-mouselistener.ts | 0 extension/src-webview/{ => stpa}/stpa-views.tsx | 6 +++--- 10 files changed, 15 insertions(+), 17 deletions(-) rename extension/src-webview/{ => fta}/fta-model.ts (100%) rename extension/src-webview/{ => fta}/fta-views.tsx (97%) rename extension/src-webview/{ => stpa}/helper-methods.ts (100%) rename extension/src-webview/{ => stpa}/stpa-model.ts (100%) rename extension/src-webview/{ => stpa}/stpa-mouselistener.ts (100%) rename extension/src-webview/{ => stpa}/stpa-views.tsx (98%) diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts index 5ebedae..2e4124d 100644 --- a/extension/src-webview/di.config.ts +++ b/extension/src-webview/di.config.ts @@ -40,9 +40,9 @@ import { import { SvgCommand } from "./actions"; import { SvgPostprocessor } from "./exportPostProcessor"; import { CustomSvgExporter } from "./exporter"; -import { FTAEdge, FTANode, FTA_EDGE_TYPE, FTA_GRAPH_TYPE, FTA_NODE_TYPE } from "./fta-model"; -import { FTAGraphView, FTANodeView, PolylineArrowEdgeViewFTA } from "./fta-views"; -import { StpaModelViewer } from "./model-viewer"; +import { FTAEdge, FTANode, FTA_EDGE_TYPE, FTA_GRAPH_TYPE, FTA_NODE_TYPE } from "./fta/fta-model"; +import { FTAGraphView, FTANodeView, PolylineArrowEdgeViewFTA } from "./fta/fta-views"; +import { PastaModelViewer } from "./model-viewer"; import { optionsModule } from "./options/options-module"; import { sidebarModule } from "./sidebar"; import { @@ -59,8 +59,8 @@ import { STPA_INTERMEDIATE_EDGE_TYPE, STPA_NODE_TYPE, STPA_PORT_TYPE, -} from "./stpa-model"; -import { StpaMouseListener } from "./stpa-mouselistener"; +} from "./stpa/stpa-model"; +import { StpaMouseListener } from "./stpa/stpa-mouselistener"; import { CSNodeView, IntermediateEdgeView, @@ -68,7 +68,7 @@ import { PortView, STPAGraphView, STPANodeView, -} from "./stpa-views"; +} from "./stpa/stpa-views"; const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); @@ -79,7 +79,7 @@ const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => undoHistoryLimit: 50, }); bind(TYPES.MouseListener).to(StpaMouseListener).inSingletonScope(); - rebind(ModelViewer).to(StpaModelViewer).inSingletonScope(); + rebind(ModelViewer).to(PastaModelViewer).inSingletonScope(); rebind(TYPES.SvgExporter).to(CustomSvgExporter).inSingletonScope(); bind(SvgPostprocessor).toSelf().inSingletonScope(); bind(TYPES.HiddenVNodePostprocessor).toService(SvgPostprocessor); diff --git a/extension/src-webview/diagram-server.ts b/extension/src-webview/diagram-server.ts index bf07fd5..6d62a35 100644 --- a/extension/src-webview/diagram-server.ts +++ b/extension/src-webview/diagram-server.ts @@ -22,7 +22,7 @@ import { VscodeLspEditDiagramServer } from "sprotty-vscode-webview/lib/lsp/editi import { SvgAction } from "./actions"; @injectable() -export class StpaDiagramServer extends VscodeLspEditDiagramServer { +export class PastaDiagramServer extends VscodeLspEditDiagramServer { protected sendMessage(message: ActionMessage): void { console.log("send to server: " + message.action.kind); diff --git a/extension/src-webview/fta-model.ts b/extension/src-webview/fta/fta-model.ts similarity index 100% rename from extension/src-webview/fta-model.ts rename to extension/src-webview/fta/fta-model.ts diff --git a/extension/src-webview/fta-views.tsx b/extension/src-webview/fta/fta-views.tsx similarity index 97% rename from extension/src-webview/fta-views.tsx rename to extension/src-webview/fta/fta-views.tsx index 71f2c95..c654328 100644 --- a/extension/src-webview/fta-views.tsx +++ b/extension/src-webview/fta/fta-views.tsx @@ -19,10 +19,8 @@ import { injectable } from 'inversify'; import { VNode } from "snabbdom"; import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SGraph, SGraphView, svg } from 'sprotty'; +import { renderAndGate, renderCircle, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "../views-rendering"; import { FTAEdge, FTANode, FTNodeType } from './fta-model'; -import { renderAndGate, renderCircle, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "./views-rendering"; - -// TODO: combine with STPA methods ?? @injectable() export class PolylineArrowEdgeViewFTA extends PolylineEdgeView { diff --git a/extension/src-webview/main.ts b/extension/src-webview/main.ts index c0ab123..5ed4f60 100644 --- a/extension/src-webview/main.ts +++ b/extension/src-webview/main.ts @@ -22,7 +22,7 @@ import { Container } from 'inversify'; import { SprottyDiagramIdentifier, VscodeDiagramServer } from 'sprotty-vscode-webview'; import { SprottyLspEditStarter } from 'sprotty-vscode-webview/lib/lsp/editing'; import { createSTPADiagramContainer } from './di.config'; -import { StpaDiagramServer } from './diagram-server'; +import { PastaDiagramServer } from './diagram-server'; export class StpaSprottyStarter extends SprottyLspEditStarter { @@ -32,7 +32,7 @@ export class StpaSprottyStarter extends SprottyLspEditStarter { protected addVscodeBindings(container: Container, diagramIdentifier: SprottyDiagramIdentifier): void { super.addVscodeBindings(container, diagramIdentifier); - container.rebind(VscodeDiagramServer).to(StpaDiagramServer); + container.rebind(VscodeDiagramServer).to(PastaDiagramServer); } } diff --git a/extension/src-webview/model-viewer.ts b/extension/src-webview/model-viewer.ts index ed35ff0..9fba565 100644 --- a/extension/src-webview/model-viewer.ts +++ b/extension/src-webview/model-viewer.ts @@ -19,7 +19,7 @@ import { inject, postConstruct } from "inversify"; import { ModelViewer } from "sprotty"; import { DISymbol } from "./di.symbols"; -export class StpaModelViewer extends ModelViewer { +export class PastaModelViewer extends ModelViewer { // @ts-ignore @inject(DISymbol.Sidebar) private sidebar: unknown; diff --git a/extension/src-webview/helper-methods.ts b/extension/src-webview/stpa/helper-methods.ts similarity index 100% rename from extension/src-webview/helper-methods.ts rename to extension/src-webview/stpa/helper-methods.ts diff --git a/extension/src-webview/stpa-model.ts b/extension/src-webview/stpa/stpa-model.ts similarity index 100% rename from extension/src-webview/stpa-model.ts rename to extension/src-webview/stpa/stpa-model.ts diff --git a/extension/src-webview/stpa-mouselistener.ts b/extension/src-webview/stpa/stpa-mouselistener.ts similarity index 100% rename from extension/src-webview/stpa-mouselistener.ts rename to extension/src-webview/stpa/stpa-mouselistener.ts diff --git a/extension/src-webview/stpa-views.tsx b/extension/src-webview/stpa/stpa-views.tsx similarity index 98% rename from extension/src-webview/stpa-views.tsx rename to extension/src-webview/stpa/stpa-views.tsx index 3345d51..94cae89 100644 --- a/extension/src-webview/stpa-views.tsx +++ b/extension/src-webview/stpa/stpa-views.tsx @@ -19,11 +19,11 @@ import { inject, injectable } from 'inversify'; import { VNode } from 'snabbdom'; import { IView, IViewArgs, Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SEdge, SGraph, SGraphView, SNode, SPort, svg, toDegrees } from 'sprotty'; -import { DISymbol } from './di.symbols'; +import { DISymbol } from '../di.symbols'; +import { ColorStyleOption, DifferentFormsOption, RenderOptionsRegistry } from '../options/render-options-registry'; +import { renderCircle, renderDiamond, renderHexagon, renderMirroredTriangle, renderPentagon, renderRectangle, renderRoundedRectangle, renderTrapez, renderTriangle } from '../views-rendering'; import { collectAllChildren } from './helper-methods'; -import { ColorStyleOption, DifferentFormsOption, RenderOptionsRegistry } from './options/render-options-registry'; import { CSEdge, CS_EDGE_TYPE, EdgeType, STPAAspect, STPAEdge, STPANode, STPA_EDGE_TYPE, STPA_INTERMEDIATE_EDGE_TYPE } from './stpa-model'; -import { renderCircle, renderDiamond, renderHexagon, renderMirroredTriangle, renderPentagon, renderRectangle, renderRoundedRectangle, renderTrapez, renderTriangle } from './views-rendering'; /** Determines if path/aspect highlighting is currently on. */ let highlighting: boolean; From 6df342d4e2696142df6e4df02b910d31fd1d0a31 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Wed, 27 Sep 2023 09:19:20 +0200 Subject: [PATCH 57/61] renaming --- extension/src-webview/di.config.ts | 6 +++--- extension/src-webview/main.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/extension/src-webview/di.config.ts b/extension/src-webview/di.config.ts index 2e4124d..1d49fee 100644 --- a/extension/src-webview/di.config.ts +++ b/extension/src-webview/di.config.ts @@ -70,7 +70,7 @@ import { STPANodeView, } from "./stpa/stpa-views"; -const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { +const pastaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => { rebind(TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); rebind(TYPES.LogLevel).toConstantValue(LogLevel.warn); rebind(TYPES.CommandStackOptions).toConstantValue({ @@ -109,10 +109,10 @@ const stpaDiagramModule = new ContainerModule((bind, unbind, isBound, rebind) => configureModelElement(context, FTA_GRAPH_TYPE, SGraph, FTAGraphView); }); -export function createSTPADiagramContainer(widgetId: string): Container { +export function createPastaDiagramContainer(widgetId: string): Container { const container = new Container(); loadDefaultModules(container); - container.load(stpaDiagramModule, sidebarModule, optionsModule); + container.load(pastaDiagramModule, sidebarModule, optionsModule); overrideViewerOptions(container, { needsClientLayout: true, needsServerLayout: true, diff --git a/extension/src-webview/main.ts b/extension/src-webview/main.ts index 5ed4f60..2e5dd6f 100644 --- a/extension/src-webview/main.ts +++ b/extension/src-webview/main.ts @@ -21,13 +21,13 @@ import 'sprotty-vscode-webview/css/sprotty-vscode.css'; import { Container } from 'inversify'; import { SprottyDiagramIdentifier, VscodeDiagramServer } from 'sprotty-vscode-webview'; import { SprottyLspEditStarter } from 'sprotty-vscode-webview/lib/lsp/editing'; -import { createSTPADiagramContainer } from './di.config'; +import { createPastaDiagramContainer } from './di.config'; import { PastaDiagramServer } from './diagram-server'; -export class StpaSprottyStarter extends SprottyLspEditStarter { +export class PastaSprottyStarter extends SprottyLspEditStarter { createContainer(diagramIdentifier: SprottyDiagramIdentifier): Container { - return createSTPADiagramContainer(diagramIdentifier.clientId); + return createPastaDiagramContainer(diagramIdentifier.clientId); } protected addVscodeBindings(container: Container, diagramIdentifier: SprottyDiagramIdentifier): void { @@ -36,5 +36,5 @@ export class StpaSprottyStarter extends SprottyLspEditStarter { } } -new StpaSprottyStarter().start(); +new PastaSprottyStarter().start(); From 1f6947c8a21de591f5f492081e48fd65e4224928 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Fri, 29 Sep 2023 13:47:03 +0200 Subject: [PATCH 58/61] single opoints of failure can be shown --- .../fta/diagram/fta-diagram-generator.ts | 12 +++++++++--- .../fta/fta-message-handler.ts | 9 +++++++++ .../fta/fta-synthesis-options.ts | 16 ++++++++++++++-- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts index 369d7a8..fc894f8 100644 --- a/extension/src-language-server/fta/diagram/fta-diagram-generator.ts +++ b/extension/src-language-server/fta/diagram/fta-diagram-generator.ts @@ -20,7 +20,7 @@ import { GeneratorContext, IdCache, LangiumDiagramGenerator } from "langium-spro import { SLabel, SModelElement, SModelRoot } from "sprotty-protocol"; import { ModelFTA, isComponent, isCondition, isKNGate } from "../../generated/ast"; import { FtaServices } from "../fta-module"; -import { FtaSynthesisOptions, noCutSet } from "../fta-synthesis-options"; +import { FtaSynthesisOptions, noCutSet, spofsSet } from "../fta-synthesis-options"; import { namedFtaElement } from "../utils"; import { FTAEdge, FTANode } from "./fta-interfaces"; import { FTA_EDGE_TYPE, FTA_GRAPH_TYPE, FTA_NODE_TYPE } from "./fta-model"; @@ -121,8 +121,14 @@ export class FtaDiagramGenerator extends LangiumDiagramGenerator { const children: SModelElement[] = this.createNodeLabel(node.name, nodeId, idCache); const description = isComponent(node) || isCondition(node) ? node.description : ""; const set = this.options.getCutSet(); - const includedInCutSet = set !== noCutSet.id ? set.includes(node.name) : false; - const notConnected = set !== noCutSet.id ? !includedInCutSet : false; + let includedInCutSet = set !== noCutSet.id ? set.includes(node.name) : false; + let notConnected = set !== noCutSet.id ? !includedInCutSet : false; + // single points of failure should be shown + if (set === spofsSet.id) { + const spofs = this.options.getSpofs(); + includedInCutSet = spofs.includes(node.name); + notConnected = false; + } const ftNode = { type: FTA_NODE_TYPE, diff --git a/extension/src-language-server/fta/fta-message-handler.ts b/extension/src-language-server/fta/fta-message-handler.ts index b10cc3b..0a2c679 100644 --- a/extension/src-language-server/fta/fta-message-handler.ts +++ b/extension/src-language-server/fta/fta-message-handler.ts @@ -73,6 +73,15 @@ async function cutSetsRequested( const model = (await getModel(uri, sharedServices)) as ModelFTA; const nodes = [model.topEvent, ...model.components, ...model.conditions, ...model.gates]; const cutSets = minimal ? determineMinimalCutSets(nodes) : determineCutSetsForFT(nodes); + // determine single points of failure + const spofs: string[] = []; + for (const cutSet of cutSets) { + if (cutSet.size === 1) { + spofs.push([...cutSet][0].name); + } + } + ftaServices.options.SynthesisOptions.setSpofs(spofs); + // create dropdown values const cutSetsString = cutSetsToString(cutSets); const dropdownValues = cutSetsString.map((cutSet) => { return { displayName: cutSet, id: cutSet }; diff --git a/extension/src-language-server/fta/fta-synthesis-options.ts b/extension/src-language-server/fta/fta-synthesis-options.ts index bb85b06..df433e1 100644 --- a/extension/src-language-server/fta/fta-synthesis-options.ts +++ b/extension/src-language-server/fta/fta-synthesis-options.ts @@ -21,6 +21,8 @@ import { SynthesisOptions } from "../synthesis-options"; const cutSetsID = "cutSets"; export const noCutSet = { displayName: "---", id: "---" }; +/* Single Point of Failure */ +export const spofsSet = { displayName: "SPoFs", id: "SPoFs" }; const cutSets: ValuedSynthesisOption = { synthesisOption: { @@ -31,12 +33,13 @@ const cutSets: ValuedSynthesisOption = { availableValues: [noCutSet], initialValue: noCutSet.id, currentValue: noCutSet.id, - values: [], + values: [noCutSet], } as DropDownOption, currentValue: noCutSet.id, }; export class FtaSynthesisOptions extends SynthesisOptions { + protected spofs: string[]; constructor() { super(); this.options = [cutSets]; @@ -49,7 +52,8 @@ export class FtaSynthesisOptions extends SynthesisOptions { updateCutSetsOption(values: { displayName: string; id: string }[]): void { const option = this.getOption(cutSetsID); if (option) { - (option.synthesisOption as DropDownOption).availableValues = [noCutSet, ...values]; + (option.synthesisOption as DropDownOption).availableValues = [noCutSet, spofsSet, ...values]; + (option.synthesisOption as DropDownOption).values = [noCutSet, spofsSet, ...values]; // if the last selected cut set is not available anymore, set the option to no cut set if (!values.find((val) => val.id === (option.synthesisOption as DropDownOption).currentId)) { (option.synthesisOption as DropDownOption).currentId = noCutSet.id; @@ -72,4 +76,12 @@ export class FtaSynthesisOptions extends SynthesisOptions { getCutSet(): string { return this.getOption(cutSetsID)?.currentValue; } + + setSpofs(spofs: string[]): void { + this.spofs = spofs; + } + + getSpofs(): string[] { + return this.spofs; + } } From f75801c15cd72329faf942616539e90308d18d06 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Fri, 29 Sep 2023 14:03:40 +0200 Subject: [PATCH 59/61] fixed scoping --- .../src-language-server/fta/fta-module.ts | 10 +++- .../fta/fta-scopeProvider.ts | 55 +++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 extension/src-language-server/fta/fta-scopeProvider.ts diff --git a/extension/src-language-server/fta/fta-module.ts b/extension/src-language-server/fta/fta-module.ts index f38f16a..76d0b4f 100644 --- a/extension/src-language-server/fta/fta-module.ts +++ b/extension/src-language-server/fta/fta-module.ts @@ -27,13 +27,17 @@ import { } from "sprotty-elk/lib/elk-layout"; import { FtaDiagramGenerator } from "./diagram/fta-diagram-generator"; import { FtaLayoutConfigurator } from "./diagram/fta-layout-config"; -import { FtaValidationRegistry, FtaValidator } from "./fta-validator"; +import { FtaScopeProvider } from "./fta-scopeProvider"; import { FtaSynthesisOptions } from "./fta-synthesis-options"; +import { FtaValidationRegistry, FtaValidator } from "./fta-validator"; /** * Declaration of custom services. */ export type FtaAddedServices = { + references: { + FtaScopeProvider: FtaScopeProvider; + }; validation: { FtaValidator: FtaValidator; }; @@ -68,6 +72,10 @@ export const FtaModule: Module new FtaScopeProvider(services), + FtaScopeProvider: (services) => new FtaScopeProvider(services), + }, validation: { ValidationRegistry: (services) => new FtaValidationRegistry(services), FtaValidator: () => new FtaValidator(), diff --git a/extension/src-language-server/fta/fta-scopeProvider.ts b/extension/src-language-server/fta/fta-scopeProvider.ts new file mode 100644 index 0000000..ebc8945 --- /dev/null +++ b/extension/src-language-server/fta/fta-scopeProvider.ts @@ -0,0 +1,55 @@ +/* + * KIELER - Kiel Integrated Environment for Layout Eclipse RichClient + * + * http://rtsys.informatik.uni-kiel.de/kieler + * + * Copyright 2023 by + * + Kiel University + * + Department of Computer Science + * + Real-Time and Embedded Systems Group + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +import { + AstNode, + AstNodeDescription, + DefaultScopeProvider, + ReferenceInfo, + Scope, + Stream, + getDocument, + stream, +} from "langium"; + +export class FtaScopeProvider extends DefaultScopeProvider { + /* override super method to exclude definitions in other files */ + getScope(context: ReferenceInfo): Scope { + const scopes: Array> = []; + const referenceType = this.reflection.getReferenceType(context); + + const precomputed = getDocument(context.container).precomputedScopes; + if (precomputed) { + let currentNode: AstNode | undefined = context.container; + do { + const allDescriptions = precomputed.get(currentNode); + if (allDescriptions.length > 0) { + scopes.push( + stream(allDescriptions).filter((desc) => this.reflection.isSubtype(desc.type, referenceType)) + ); + } + currentNode = currentNode.$container; + } while (currentNode); + } + + let result: Scope = this.createScope(scopes[scopes.length - 1]); + for (let i = scopes.length - 2; i >= 0; i--) { + result = this.createScope(scopes[i], result); + } + return result; + } +} From 03f25ab1eee219485737fc312933b9fee5009a25 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Thu, 5 Oct 2023 17:28:47 +0200 Subject: [PATCH 60/61] improved gate rendering --- extension/src-webview/fta/fta-views.tsx | 6 ++--- extension/src-webview/stpa/stpa-views.tsx | 4 ++-- extension/src-webview/views-rendering.tsx | 28 +++++++++++++++-------- 3 files changed, 23 insertions(+), 15 deletions(-) diff --git a/extension/src-webview/fta/fta-views.tsx b/extension/src-webview/fta/fta-views.tsx index c654328..1621579 100644 --- a/extension/src-webview/fta/fta-views.tsx +++ b/extension/src-webview/fta/fta-views.tsx @@ -19,7 +19,7 @@ import { injectable } from 'inversify'; import { VNode } from "snabbdom"; import { Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SGraph, SGraphView, svg } from 'sprotty'; -import { renderAndGate, renderCircle, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "../views-rendering"; +import { renderAndGate, renderOval, renderInhibitGate, renderKnGate, renderOrGate, renderRectangle } from "../views-rendering"; import { FTAEdge, FTANode, FTNodeType } from './fta-model'; @injectable() @@ -49,10 +49,10 @@ export class FTANodeView extends RectangularNodeView { element = renderRectangle(node); break; case (FTNodeType.COMPONENT || FTNodeType.CONDITION): - element = renderCircle(node); + element = renderOval(node); break; case FTNodeType.CONDITION: - element = renderCircle(node); + element = renderOval(node); break; case FTNodeType.AND: element = renderAndGate(node); diff --git a/extension/src-webview/stpa/stpa-views.tsx b/extension/src-webview/stpa/stpa-views.tsx index 94cae89..4b70a21 100644 --- a/extension/src-webview/stpa/stpa-views.tsx +++ b/extension/src-webview/stpa/stpa-views.tsx @@ -21,7 +21,7 @@ import { VNode } from 'snabbdom'; import { IView, IViewArgs, Point, PolylineEdgeView, RectangularNodeView, RenderingContext, SEdge, SGraph, SGraphView, SNode, SPort, svg, toDegrees } from 'sprotty'; import { DISymbol } from '../di.symbols'; import { ColorStyleOption, DifferentFormsOption, RenderOptionsRegistry } from '../options/render-options-registry'; -import { renderCircle, renderDiamond, renderHexagon, renderMirroredTriangle, renderPentagon, renderRectangle, renderRoundedRectangle, renderTrapez, renderTriangle } from '../views-rendering'; +import { renderOval, renderDiamond, renderHexagon, renderMirroredTriangle, renderPentagon, renderRectangle, renderRoundedRectangle, renderTrapez, renderTriangle } from '../views-rendering'; import { collectAllChildren } from './helper-methods'; import { CSEdge, CS_EDGE_TYPE, EdgeType, STPAAspect, STPAEdge, STPANode, STPA_EDGE_TYPE, STPA_INTERMEDIATE_EDGE_TYPE } from './stpa-model'; @@ -144,7 +144,7 @@ export class STPANodeView extends RectangularNodeView { element = renderPentagon(node); break; case STPAAspect.UCA: - element = renderCircle(node); + element = renderOval(node); break; case STPAAspect.CONTROLLERCONSTRAINT: element = renderMirroredTriangle(node); diff --git a/extension/src-webview/views-rendering.tsx b/extension/src-webview/views-rendering.tsx index bc978de..671d6ca 100644 --- a/extension/src-webview/views-rendering.tsx +++ b/extension/src-webview/views-rendering.tsx @@ -24,11 +24,16 @@ import { SNode, svg } from 'sprotty'; * @param node The node that should be represented by a circle. * @returns A circle for {@code node}. */ -export function renderCircle(node: SNode): VNode { - return ; + /* return ; + />; */ } /** @@ -177,12 +182,15 @@ export function renderHexagon(node: SNode): VNode { */ export function renderAndGate(node: SNode): VNode { const leftX = 0; + const midX = Math.max(node.size.width, 0) / 2.0; const rightX = Math.max(node.size.width, 0); const botY = Math.max(node.size.height, 0); const midY = Math.max(node.size.height, 0) / 2.0; const topY = 0; - const d = 'M' + leftX + " " + botY + " L " + leftX + " " + midY + " C " + leftX + " " + topY + " " + rightX + " " - + topY + " " + rightX + " " + midY + " L " + rightX + " " + botY + 'Z'; + + const d = `M ${leftX}, ${midY} V ${botY} H ${rightX} V ${midY} C ${rightX}, ${midY} ${rightX}, ${topY} ${midX}, ${topY} ${leftX}, ${topY} ${leftX}, ${midY} ${leftX}, ${midY} Z`; + // 'M' + leftX + " " + midY + " V " + botY + " H " + rightX + " V " + midY + " C " + rightX + " " + midY + " " + rightX + " " + // + topY + " " + midX + " " + topY + " " + leftX + " " + topY + " " + leftX + " " + midY + " " + leftX + " " + midY + 'Z'; return From 6d6485cd9978383d83d794a234e4c318bda976b2 Mon Sep 17 00:00:00 2001 From: Jette Petzold Date: Fri, 13 Oct 2023 14:46:10 +0200 Subject: [PATCH 61/61] mka feedback --- extension/package.json | 2 +- .../fta/diagram/fta-layout-config.ts | 4 +- extension/src-webview/css/fta-diagram.css | 4 +- .../options/render-options-registry.ts | 1 - extension/src-webview/views-rendering.tsx | 49 ++++++++----------- extension/src/extension.ts | 18 ++++++- yarn.lock | 8 +-- 7 files changed, 46 insertions(+), 40 deletions(-) diff --git a/extension/package.json b/extension/package.json index 225afbc..223346b 100644 --- a/extension/package.json +++ b/extension/package.json @@ -313,7 +313,7 @@ "editor/title": [ { "command": "pasta.diagram.open", - "when": "editorLangId == 'stpa' || editorLangId == 'fta'", + "when": "editorLangId in pasta.languages", "group": "navigation" }, { diff --git a/extension/src-language-server/fta/diagram/fta-layout-config.ts b/extension/src-language-server/fta/diagram/fta-layout-config.ts index 471adc0..004cb9a 100644 --- a/extension/src-language-server/fta/diagram/fta-layout-config.ts +++ b/extension/src-language-server/fta/diagram/fta-layout-config.ts @@ -20,14 +20,14 @@ import { DefaultLayoutConfigurator } from "sprotty-elk/lib/elk-layout"; import { SGraph, SModelIndex, SNode } from "sprotty-protocol"; export class FtaLayoutConfigurator extends DefaultLayoutConfigurator { - protected graphOptions(sgraph: SGraph, index: SModelIndex): LayoutOptions { + protected graphOptions(_sgraph: SGraph, _index: SModelIndex): LayoutOptions { return { "org.eclipse.elk.direction": "DOWN", "org.eclipse.elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX", }; } - protected nodeOptions(snode: SNode, index: SModelIndex): LayoutOptions | undefined { + protected nodeOptions(_snode: SNode, _index: SModelIndex): LayoutOptions | undefined { return { "org.eclipse.elk.nodeLabels.placement": "INSIDE V_CENTER H_CENTER", }; diff --git a/extension/src-webview/css/fta-diagram.css b/extension/src-webview/css/fta-diagram.css index 68a9d13..edb0f76 100644 --- a/extension/src-webview/css/fta-diagram.css +++ b/extension/src-webview/css/fta-diagram.css @@ -38,8 +38,8 @@ } .fta-text{ - fill: var(--fta-kn-dark); - stroke-width: 0.5; + fill: dimgrey; + stroke-width: 0; font-size: 10px; } diff --git a/extension/src-webview/options/render-options-registry.ts b/extension/src-webview/options/render-options-registry.ts index 95ad0d1..9e74fc2 100644 --- a/extension/src-webview/options/render-options-registry.ts +++ b/extension/src-webview/options/render-options-registry.ts @@ -89,7 +89,6 @@ export class RenderOptionsRegistry extends Registry { handle(action: Action): void | Action | ICommand { if (SetRenderOptionAction.isThisAction(action)) { const option = this._renderOptions.get(action.id); - if (!option) {return;} option.currentValue = action.value; const sendAction = { kind: SendConfigAction.KIND, options: [{ id: action.id, value: action.value }] }; diff --git a/extension/src-webview/views-rendering.tsx b/extension/src-webview/views-rendering.tsx index 671d6ca..48fea54 100644 --- a/extension/src-webview/views-rendering.tsx +++ b/extension/src-webview/views-rendering.tsx @@ -30,10 +30,6 @@ export function renderOval(node: SNode): VNode { cy={Math.max(node.size.height, 0) / 2.0} rx={Math.max(nodeWidth, 0) / 2.0} ry={Math.max(node.size.height, 0) / 2.0} />; - /* return ; */ } /** @@ -73,9 +69,9 @@ export function renderTriangle(node: SNode): VNode { const rightX = Math.max(node.size.width, 0); const botY = Math.max(node.size.height, 0); const topY = 0; - const d = 'M' + leftX + " " + botY + " L " + midX + " " + topY + " L " + rightX + " " + botY + 'Z'; + const path = 'M' + leftX + " " + botY + " L " + midX + " " + topY + " L " + rightX + " " + botY + 'Z'; return ; } @@ -90,9 +86,9 @@ export function renderMirroredTriangle(node: SNode): VNode { const rightX = Math.max(node.size.width, 0); const botY = Math.max(node.size.height, 0); const topY = 0; - const d = 'M' + leftX + " " + topY + " L " + midX + " " + botY + " L " + rightX + " " + topY + 'Z'; + const path = 'M' + leftX + " " + topY + " L " + midX + " " + botY + " L " + rightX + " " + topY + 'Z'; return ; } @@ -108,10 +104,10 @@ export function renderTrapez(node: SNode): VNode { const rightX = Math.max(node.size.width, 0); const botY = Math.max(node.size.height, 0); const topY = 0; - const d = 'M' + leftX + " " + botY + " L " + midX1 + " " + topY + " L " + midX2 + " " + topY + const path = 'M' + leftX + " " + botY + " L " + midX1 + " " + topY + " L " + midX2 + " " + topY + " L " + rightX + " " + botY + 'Z'; return ; } @@ -127,10 +123,10 @@ export function renderDiamond(node: SNode): VNode { const topY = 0; const midY = Math.max(node.size.height, 0) / 2.0; const botY = Math.max(node.size.height, 0); - const d = 'M' + leftX + " " + midY + " L " + midX + " " + topY + " L " + rightX + " " + midY + const path = 'M' + leftX + " " + midY + " L " + midX + " " + topY + " L " + rightX + " " + midY + " L " + midX + " " + botY + 'Z'; return ; } @@ -148,10 +144,10 @@ export function renderPentagon(node: SNode): VNode { const topY = 0; const midY = Math.max(node.size.height, 0) / 3.0; const botY = Math.max(node.size.height, 0); - const d = 'M' + startX + " " + botY + " L " + leftX + " " + midY + " L " + midX + " " + topY + const path = 'M' + startX + " " + botY + " L " + leftX + " " + midY + " L " + midX + " " + topY + " L " + rightX + " " + midY + " L " + endX + " " + botY + 'Z'; return ; } @@ -168,10 +164,10 @@ export function renderHexagon(node: SNode): VNode { const topY = 0; const midY = Math.max(node.size.height, 0) / 2.0; const botY = Math.max(node.size.height, 0); - const d = 'M' + leftX + " " + midY + " L " + midX1 + " " + botY + " L " + midX2 + " " + botY + const path = 'M' + leftX + " " + midY + " L " + midX1 + " " + botY + " L " + midX2 + " " + botY + " L " + rightX + " " + midY + " L " + midX2 + " " + topY + " L " + midX1 + " " + topY + 'Z'; return ; } @@ -188,12 +184,10 @@ export function renderAndGate(node: SNode): VNode { const midY = Math.max(node.size.height, 0) / 2.0; const topY = 0; - const d = `M ${leftX}, ${midY} V ${botY} H ${rightX} V ${midY} C ${rightX}, ${midY} ${rightX}, ${topY} ${midX}, ${topY} ${leftX}, ${topY} ${leftX}, ${midY} ${leftX}, ${midY} Z`; - // 'M' + leftX + " " + midY + " V " + botY + " H " + rightX + " V " + midY + " C " + rightX + " " + midY + " " + rightX + " " - // + topY + " " + midX + " " + topY + " " + leftX + " " + topY + " " + leftX + " " + midY + " " + leftX + " " + midY + 'Z'; + const path = `M ${leftX}, ${midY} V ${botY} H ${rightX} V ${midY} C ${rightX}, ${midY} ${rightX}, ${topY} ${midX}, ${topY} ${leftX}, ${topY} ${leftX}, ${midY} ${leftX}, ${midY} Z`; return ; } @@ -210,11 +204,11 @@ export function renderOrGate(node: SNode): VNode { const nearBotY = botY - 5; const midY = Math.max(node.size.height, 0) / 2; const topY = 0; - const d = `M${leftX},${midY} V ${botY}` + `C ${leftX}, ${botY} ${leftX+10}, ${nearBotY} ${midX}, ${nearBotY} ${rightX-10}, ${nearBotY} ${rightX}, ${botY} ${rightX}, ${botY}` + const path = `M${leftX},${midY} V ${botY}` + `C ${leftX}, ${botY} ${leftX+10}, ${nearBotY} ${midX}, ${nearBotY} ${rightX-10}, ${nearBotY} ${rightX}, ${botY} ${rightX}, ${botY}` + `V ${midY} A ${node.size.width},${node.size.height-10},${0},${0},${0},${midX},${topY} A ${node.size.width},${node.size.height-10},${0},${0},${0},${leftX},${midY} Z`; return ; } @@ -231,13 +225,13 @@ export function renderKnGate(node: SNode, k: number, n: number): VNode { const nearBotY = Math.max(node.size.height, 0) - (Math.max(node.size.height, 0) / 10.0); const midY = Math.max(node.size.height, 0) / 2; const topY = 0; - const d = `M${leftX},${midY} V ${botY}` + `C ${leftX}, ${botY} ${leftX+10}, ${nearBotY} ${midX}, ${nearBotY} ${rightX-10}, ${nearBotY} ${rightX}, ${botY} ${rightX}, ${botY}` + const path = `M${leftX},${midY} V ${botY}` + `C ${leftX}, ${botY} ${leftX+10}, ${nearBotY} ${midX}, ${nearBotY} ${rightX-10}, ${nearBotY} ${rightX}, ${botY} ${rightX}, ${botY}` + `V ${midY} A ${node.size.width},${node.size.height-10},${0},${0},${0},${midX},${topY} A ${node.size.width},${node.size.height-10},${0},${0},${0},${leftX},${midY} Z`; return ( - - + + {`${k}/${n}`} @@ -258,10 +252,9 @@ export function renderInhibitGate(node: SNode): VNode { const highY = Math.max(node.size.height, 0) / 4.0; const highestY = 0; - const d = 'M' + leftX + " " + lowY + " L " + leftX + " " + highY + " L " + midX + " " + highestY + " L " + rightX - + " " + highY + " L " + rightX + " " + lowY + " L " + midX + " " + lowestY + "Z"; + const path = `M${leftX},${lowY} L ${leftX} ${highY} L ${midX} ${highestY} L ${rightX} ${highY} L ${rightX} ${lowY} L ${midX} ${lowestY} Z`; return ; } \ No newline at end of file diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 3c80375..484ca26 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -31,6 +31,13 @@ import { createOutputChannel, createQuickPickForWorkspaceOptions } from './utils let languageClient: LanguageClient; +/** + * All file endings of the languages that are supported by pasta. + * The file ending should also be the language id, since it is also used to + * register document selectors in the language client. + */ +const supportedFileEndings = ['stpa', 'fta']; + export function activate(context: vscode.ExtensionContext): void { vscode.window.showInformationMessage('Activating STPA extension'); @@ -40,6 +47,8 @@ export function activate(context: vscode.ExtensionContext): void { } languageClient = createLanguageClient(context); + // Create context key of supported languages + vscode.commands.executeCommand('setContext', 'pasta.languages', supportedFileEndings); if (diagramMode === 'panel') { // Set up webview panel manager for freestyle webviews @@ -53,6 +62,7 @@ export function activate(context: vscode.ExtensionContext): void { registerDefaultCommands(webviewPanelManager, context, { extensionPrefix: 'pasta' }); registerTextEditorSync(webviewPanelManager, context); registerSTPACommands(webviewPanelManager, context, { extensionPrefix: 'pasta' }); + registerFTACommands(webviewPanelManager, context, { extensionPrefix: 'pasta' }); } if (diagramMode === 'editor') { @@ -186,7 +196,9 @@ function registerSTPACommands(manager: StpaLspVscodeExtension, context: vscode.E return formulas; }) ); +} +function registerFTACommands(manager: StpaLspVscodeExtension, context: vscode.ExtensionContext, options: { extensionPrefix: string; }): void { // commands for computing and displaying the (minimal) cut sets of the fault tree. context.subscriptions.push( vscode.commands.registerCommand(options.extensionPrefix + ".fta.cutSets", async (uri: vscode.Uri) => { @@ -228,8 +240,10 @@ function createLanguageClient(context: vscode.ExtensionContext): LanguageClient // Options to control the language client const clientOptions: LanguageClientOptions = { - documentSelector: [{ scheme: 'file', language: 'stpa' }, - { scheme: 'file', language: 'fta' }], + documentSelector: supportedFileEndings.map((ending) => ({ + scheme: 'file', + language: ending, + })), synchronize: { // Notify the server about file changes to files contained in the workspace fileEvents: fileSystemWatcher diff --git a/yarn.lock b/yarn.lock index 5f0fa11..0c932d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -238,10 +238,10 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@kieler/table-webview@^0.0.3": - version "0.0.3" - resolved "https://registry.yarnpkg.com/@kieler/table-webview/-/table-webview-0.0.3.tgz#33199d9b0d8cd88d0aad6d4230617b94942482aa" - integrity sha512-XiDfn/MwHzVEpXLWC5DT6Ysg/5Zke3GlbtjBDDPRD1mLFXIekOCxkGYAKu068djqSAg3hsoiIhwLwWBfm48VNQ== +"@kieler/table-webview@^0.0.4": + version "0.0.4" + resolved "https://registry.yarnpkg.com/@kieler/table-webview/-/table-webview-0.0.4.tgz#c68a0652423a3c5f74a635fc5432e1430f758ee3" + integrity sha512-ZUAdX8dUCq72UdpFJz61bPX8eoJqnRuiJBOIFgOb+NqUke13zo4QmkeapmgA4zSKFBx5GC6nhSDQvVr9CMD4AQ== dependencies: "@types/vscode" "^1.56.0" reflect-metadata "^0.1.13"