From 353fc84ad97946ae41af2a6bf79a7de6c3cf49c8 Mon Sep 17 00:00:00 2001 From: Martin Fleck Date: Fri, 9 Aug 2024 13:25:45 +0000 Subject: [PATCH] Support custom properties in textual representation (#62) * Support custom properties in textual representation - Add optional custom properties to most elements in the grammar * Update macos-11 for CI run From GitHub: The macos-11 label has been deprecated and will no longer be available after 28 June 2024. --- .github/workflows/build-and-test.yml | 2 +- .../ExampleDWH/mappings/DWH.mapping.cm | 18 +- .../ExampleCRM/diagrams/CRM.diagram.cm | 14 +- .../ExampleCRM/entities/Address.entity.cm | 10 +- .../Address_Customer.relationship.cm | 10 +- .../src/language-server/cross-model-scope.ts | 57 +- .../language-server/cross-model-serializer.ts | 3 +- .../src/language-server/entity.langium | 6 +- .../src/language-server/generated/ast.ts | 93 +++- .../src/language-server/generated/grammar.ts | 489 +++++++++++++++--- .../src/language-server/mapping.langium | 18 +- .../src/language-server/relationship.langium | 2 + .../language-server/system-diagram.langium | 5 +- .../src/language-server/terminals.langium | 17 + .../src/language-server/util/ast-util.ts | 18 +- .../syntaxes/cross-model.tmLanguage.json | 2 +- .../serializer/cross-model-serializer.test.ts | 48 +- .../protocol/src/model-service/protocol.ts | 29 +- 18 files changed, 691 insertions(+), 150 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 00f85c46..1747ece7 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-2019, ubuntu-latest, macos-11] + os: [windows-2019, ubuntu-latest, macos-12] runs-on: ${{ matrix.os }} timeout-minutes: 60 diff --git a/examples/mapping-example/ExampleDWH/mappings/DWH.mapping.cm b/examples/mapping-example/ExampleDWH/mappings/DWH.mapping.cm index 094aa15e..f029cee2 100644 --- a/examples/mapping-example/ExampleDWH/mappings/DWH.mapping.cm +++ b/examples/mapping-example/ExampleDWH/mappings/DWH.mapping.cm @@ -34,6 +34,10 @@ mapping: - Customer conditions: - CalcAgeSourceObject.BirthDate = Customer.BirthDate + customProperties: + - name: Author + value: "CrossBreeze" + - name: ExampleMappingSource target: entity: CompleteCustomer mappings: @@ -52,4 +56,16 @@ mapping: expression: "1337" - attribute: FixedString expression: "Fixed String" - - attribute: Today \ No newline at end of file + - attribute: Today + customProperties: + - name: Author + value: "CrossBreeze" + - name: ExampleEntityTargetAttribute + customProperties: + - name: Author + value: "CrossBreeze" + - name: ExampleMappingTarget + customProperties: + - name: Author + value: "CrossBreeze" + - name: ExampleMapping \ No newline at end of file diff --git a/examples/mapping-example/Sources/ExampleCRM/diagrams/CRM.diagram.cm b/examples/mapping-example/Sources/ExampleCRM/diagrams/CRM.diagram.cm index 9fba419a..e466e842 100644 --- a/examples/mapping-example/Sources/ExampleCRM/diagrams/CRM.diagram.cm +++ b/examples/mapping-example/Sources/ExampleCRM/diagrams/CRM.diagram.cm @@ -21,6 +21,10 @@ systemDiagram: y: 319 width: 159.649658203125 height: 96 + customProperties: + - name: Author + value: "CrossBreeze" + - name: ExampleNode edges: - id: CustomerToOrder relationship: Order_Customer @@ -29,4 +33,12 @@ systemDiagram: - id: AddressToCustomer relationship: ExampleCRM.Address_Customer sourceNode: AddressNode - targetNode: CustomerNode \ No newline at end of file + targetNode: CustomerNode + customProperties: + - name: Author + value: "CrossBreeze" + - name: ExampleEdge + customProperties: + - name: Author + value: "CrossBreeze" + - name: ExampleDiagram \ No newline at end of file diff --git a/examples/mapping-example/Sources/ExampleCRM/entities/Address.entity.cm b/examples/mapping-example/Sources/ExampleCRM/entities/Address.entity.cm index 6e3d6ddb..b61297bd 100644 --- a/examples/mapping-example/Sources/ExampleCRM/entities/Address.entity.cm +++ b/examples/mapping-example/Sources/ExampleCRM/entities/Address.entity.cm @@ -12,4 +12,12 @@ entity: datatype: "Text" - id: CountryCode name: "CountryCode" - datatype: "Text" \ No newline at end of file + datatype: "Text" + customProperties: + - name: Author + value: "CrossBreeze" + - name: ExampleEntityAttribute + customProperties: + - name: Author + value: "CrossBreeze" + - name: ExampleEntity \ No newline at end of file diff --git a/examples/mapping-example/Sources/ExampleCRM/relationships/Address_Customer.relationship.cm b/examples/mapping-example/Sources/ExampleCRM/relationships/Address_Customer.relationship.cm index 683fdc9d..c14516c7 100644 --- a/examples/mapping-example/Sources/ExampleCRM/relationships/Address_Customer.relationship.cm +++ b/examples/mapping-example/Sources/ExampleCRM/relationships/Address_Customer.relationship.cm @@ -6,4 +6,12 @@ relationship: type: "1:1" attributes: - parent: Customer.Id - child: ExampleCRM.Address.CustomerID \ No newline at end of file + child: ExampleCRM.Address.CustomerID + customProperties: + - name: Author + value: "CrossBreeze" + - name: ExampleRelationshipAttribute + customProperties: + - name: Author + value: "CrossBreeze" + - name: ExampleRelationship \ No newline at end of file diff --git a/extensions/crossmodel-lang/src/language-server/cross-model-scope.ts b/extensions/crossmodel-lang/src/language-server/cross-model-scope.ts index 1bb62421..483f9b5e 100644 --- a/extensions/crossmodel-lang/src/language-server/cross-model-scope.ts +++ b/extensions/crossmodel-lang/src/language-server/cross-model-scope.ts @@ -2,12 +2,21 @@ * Copyright (c) 2023 CrossBreeze. ********************************************************************************/ -import { AstNode, AstNodeDescription, DefaultScopeComputation, LangiumDocument, PrecomputedScopes, streamAllContents } from 'langium'; +import { + AstNode, + AstNodeDescription, + DefaultScopeComputation, + LangiumDocument, + PrecomputedScopes, + Reference, + streamAllContents +} from 'langium'; import { CancellationToken } from 'vscode-jsonrpc'; import { CrossModelServices } from './cross-model-module.js'; import { DefaultIdProvider, combineIds } from './cross-model-naming.js'; import { CrossModelPackageManager, UNKNOWN_PROJECT_ID, UNKNOWN_PROJECT_REFERENCE } from './cross-model-package-manager.js'; import { + Entity, EntityNode, EntityNodeAttribute, SourceObject, @@ -18,7 +27,7 @@ import { isSourceObject, isTargetObject } from './generated/ast.js'; -import { findDocument, setAttributes, setImplicitId, setOwner } from './util/ast-util.js'; +import { fixDocument, setAttributes, setImplicitId, setOwner } from './util/ast-util.js'; /** * Custom node description that wraps a given description under a potentially new name and also stores the package id for faster access. @@ -123,43 +132,53 @@ export class CrossModelScopeComputation extends DefaultScopeComputation { this.processSourceObject(node, id, document).forEach(description => scopes.add(container, description)); } } - if (isTargetObject(node) && node.entity?.ref?.id) { - this.processTargetObject(node, node.entity?.ref.id, document).forEach(description => scopes.add(container, description)); + if (isTargetObject(node)) { + const entity = this.getEntity(node, document); + if (entity?.id) { + this.processTargetObject(node, entity.id, document).forEach(description => scopes.add(container, description)); + } } } } protected processEntityNode(node: EntityNode, nodeId: string, document: LangiumDocument): AstNodeDescription[] { - try { - // TODO: Check if this is still necessary - if (node.entity?.ref) { - findDocument(node.entity.ref); - } - } catch (error) { - console.error(error); + const entity = this.getEntity(node, document); + if (!entity) { return []; } const attributes = - node.entity?.ref?.attributes.map(attribute => setOwner({ ...attribute, $type: EntityNodeAttribute }, node)) ?? - []; + entity.attributes.map(attribute => setOwner({ ...attribute, $type: EntityNodeAttribute }, node)) ?? []; setAttributes(node, attributes); return attributes.map(attribute => this.descriptions.createDescription(attribute, combineIds(nodeId, attribute.id), document)); } + protected getEntity(node: AstNode & { entity: Reference }, document: LangiumDocument): Entity | undefined { + try { + return fixDocument(node, document).entity?.ref; + } catch (error) { + console.error(error); + return undefined; + } + } + protected processSourceObject(node: SourceObject, nodeId: string, document: LangiumDocument): AstNodeDescription[] { + const entity = this.getEntity(node, document); + if (!entity) { + return []; + } const attributes = - node.entity?.ref?.attributes.map(attribute => - setOwner({ ...attribute, $type: SourceObjectAttribute }, node) - ) ?? []; + entity.attributes.map(attribute => setOwner({ ...attribute, $type: SourceObjectAttribute }, node)) ?? []; setAttributes(node, attributes); return attributes.map(attribute => this.descriptions.createDescription(attribute, combineIds(nodeId, attribute.id), document)); } protected processTargetObject(node: TargetObject, nodeId: string, document: LangiumDocument): AstNodeDescription[] { + const entity = this.getEntity(node, document); + if (!entity) { + return []; + } const attributes = - node.entity?.ref?.attributes.map(attribute => - setOwner({ ...attribute, $type: TargetObjectAttribute }, node) - ) ?? []; + entity.attributes.map(attribute => setOwner({ ...attribute, $type: TargetObjectAttribute }, node)) ?? []; setImplicitId(node, nodeId); setAttributes(node, attributes); // for target attributes, we use simple names and not object-qualified ones diff --git a/extensions/crossmodel-lang/src/language-server/cross-model-serializer.ts b/extensions/crossmodel-lang/src/language-server/cross-model-serializer.ts index 1aaf7036..6fb089dc 100644 --- a/extensions/crossmodel-lang/src/language-server/cross-model-serializer.ts +++ b/extensions/crossmodel-lang/src/language-server/cross-model-serializer.ts @@ -49,7 +49,8 @@ const PROPERTY_ORDER = [ 'mappings', 'source', 'conditions', - 'expression' + 'expression', + 'customProperties' ]; const ID_OR_IDREF = [ diff --git a/extensions/crossmodel-lang/src/language-server/entity.langium b/extensions/crossmodel-lang/src/language-server/entity.langium index f02f7d87..58739c58 100644 --- a/extensions/crossmodel-lang/src/language-server/entity.langium +++ b/extensions/crossmodel-lang/src/language-server/entity.langium @@ -12,6 +12,7 @@ Entity: ('-' attributes+=EntityAttribute)+ DEDENT )? + CustomProperties? DEDENT ; @@ -22,7 +23,7 @@ interface Attribute { description?: string; } -interface EntityAttribute extends Attribute { +interface EntityAttribute extends Attribute, WithCustomProperties { identifier?: boolean; } @@ -31,4 +32,5 @@ EntityAttribute returns EntityAttribute: 'name' ':' name=STRING 'datatype' ':' datatype=STRING (identifier?='identifier' ':' ('TRUE' | 'true'))? - ('description' ':' description=STRING)?; + ('description' ':' description=STRING)? + CustomProperties?; diff --git a/extensions/crossmodel-lang/src/language-server/generated/ast.ts b/extensions/crossmodel-lang/src/language-server/generated/ast.ts index 6939f785..04845c8b 100644 --- a/extensions/crossmodel-lang/src/language-server/generated/ast.ts +++ b/extensions/crossmodel-lang/src/language-server/generated/ast.ts @@ -64,6 +64,7 @@ export interface AttributeMapping extends AstNode { readonly $container: TargetObject; readonly $type: 'AttributeMapping'; attribute: AttributeMappingTarget + customProperties: Array expression?: string sources: Array } @@ -126,10 +127,24 @@ export function isCrossModelRoot(item: unknown): item is CrossModelRoot { return reflection.isInstance(item, CrossModelRoot); } +export interface CustomProperty extends AstNode { + readonly $container: AttributeMapping | Entity | EntityNode | Mapping | Relationship | RelationshipAttribute | RelationshipEdge | SourceObject | SystemDiagram | TargetObject | WithCustomProperties; + readonly $type: 'CustomProperty'; + name: string + value?: string +} + +export const CustomProperty = 'CustomProperty'; + +export function isCustomProperty(item: unknown): item is CustomProperty { + return reflection.isInstance(item, CustomProperty); +} + export interface Entity extends AstNode { readonly $container: CrossModelRoot; readonly $type: 'Entity'; attributes: Array + customProperties: Array description?: string id: string name?: string @@ -144,6 +159,7 @@ export function isEntity(item: unknown): item is Entity { export interface EntityNode extends AstNode { readonly $container: SystemDiagram; readonly $type: 'EntityNode'; + customProperties: Array description?: string entity: Reference height: number @@ -175,6 +191,7 @@ export function isJoinCondition(item: unknown): item is JoinCondition { export interface Mapping extends AstNode { readonly $container: CrossModelRoot; readonly $type: 'Mapping'; + customProperties: Array id: string sources: Array target: TargetObject @@ -203,6 +220,7 @@ export interface Relationship extends AstNode { readonly $type: 'Relationship'; attributes: Array child: Reference + customProperties: Array description?: string id: string name?: string @@ -220,6 +238,7 @@ export interface RelationshipAttribute extends AstNode { readonly $container: Relationship; readonly $type: 'RelationshipAttribute'; child: Reference + customProperties: Array parent: Reference } @@ -232,6 +251,7 @@ export function isRelationshipAttribute(item: unknown): item is RelationshipAttr export interface RelationshipEdge extends AstNode { readonly $container: SystemDiagram; readonly $type: 'RelationshipEdge'; + customProperties: Array id: string relationship: Reference sourceNode: Reference @@ -248,6 +268,7 @@ export interface SourceObject extends AstNode { readonly $container: Mapping; readonly $type: 'SourceObject'; conditions: Array + customProperties: Array dependencies: Array entity: Reference id: string @@ -299,6 +320,7 @@ export function isStringLiteral(item: unknown): item is StringLiteral { export interface SystemDiagram extends AstNode { readonly $container: CrossModelRoot; readonly $type: 'SystemDiagram'; + customProperties: Array description?: string edges: Array id?: string @@ -315,6 +337,7 @@ export function isSystemDiagram(item: unknown): item is SystemDiagram { export interface TargetObject extends AstNode { readonly $container: Mapping; readonly $type: 'TargetObject'; + customProperties: Array entity: Reference mappings: Array } @@ -325,7 +348,18 @@ export function isTargetObject(item: unknown): item is TargetObject { return reflection.isInstance(item, TargetObject); } -export interface EntityAttribute extends Attribute { +export interface WithCustomProperties extends AstNode { + readonly $type: 'EntityAttribute' | 'EntityNodeAttribute' | 'SourceObjectAttribute' | 'TargetObjectAttribute' | 'WithCustomProperties'; + customProperties: Array +} + +export const WithCustomProperties = 'WithCustomProperties'; + +export function isWithCustomProperties(item: unknown): item is WithCustomProperties { + return reflection.isInstance(item, WithCustomProperties); +} + +export interface EntityAttribute extends Attribute, WithCustomProperties { readonly $type: 'EntityAttribute' | 'EntityNodeAttribute'; identifier: boolean } @@ -336,7 +370,7 @@ export function isEntityAttribute(item: unknown): item is EntityAttribute { return reflection.isInstance(item, EntityAttribute); } -export interface SourceObjectAttribute extends Attribute { +export interface SourceObjectAttribute extends Attribute, WithCustomProperties { readonly $type: 'SourceObjectAttribute'; } @@ -346,7 +380,7 @@ export function isSourceObjectAttribute(item: unknown): item is SourceObjectAttr return reflection.isInstance(item, SourceObjectAttribute); } -export interface TargetObjectAttribute extends Attribute { +export interface TargetObjectAttribute extends Attribute, WithCustomProperties { readonly $type: 'TargetObjectAttribute'; } @@ -356,7 +390,7 @@ export function isTargetObjectAttribute(item: unknown): item is TargetObjectAttr return reflection.isInstance(item, TargetObjectAttribute); } -export interface EntityNodeAttribute extends EntityAttribute { +export interface EntityNodeAttribute extends EntityAttribute, WithCustomProperties { readonly $type: 'EntityNodeAttribute'; } @@ -374,6 +408,7 @@ export type CrossModelAstType = { BinaryExpression: BinaryExpression BooleanExpression: BooleanExpression CrossModelRoot: CrossModelRoot + CustomProperty: CustomProperty Entity: Entity EntityAttribute: EntityAttribute EntityNode: EntityNode @@ -393,12 +428,13 @@ export type CrossModelAstType = { SystemDiagram: SystemDiagram TargetObject: TargetObject TargetObjectAttribute: TargetObjectAttribute + WithCustomProperties: WithCustomProperties } export class CrossModelAstReflection extends AbstractAstReflection { getAllTypes(): string[] { - return ['Attribute', 'AttributeMapping', 'AttributeMappingSource', 'AttributeMappingTarget', 'BinaryExpression', 'BooleanExpression', 'CrossModelRoot', 'Entity', 'EntityAttribute', 'EntityNode', 'EntityNodeAttribute', 'JoinCondition', 'Mapping', 'NumberLiteral', 'Relationship', 'RelationshipAttribute', 'RelationshipEdge', 'SourceObject', 'SourceObjectAttribute', 'SourceObjectAttributeReference', 'SourceObjectCondition', 'SourceObjectDependency', 'StringLiteral', 'SystemDiagram', 'TargetObject', 'TargetObjectAttribute']; + return ['Attribute', 'AttributeMapping', 'AttributeMappingSource', 'AttributeMappingTarget', 'BinaryExpression', 'BooleanExpression', 'CrossModelRoot', 'CustomProperty', 'Entity', 'EntityAttribute', 'EntityNode', 'EntityNodeAttribute', 'JoinCondition', 'Mapping', 'NumberLiteral', 'Relationship', 'RelationshipAttribute', 'RelationshipEdge', 'SourceObject', 'SourceObjectAttribute', 'SourceObjectAttributeReference', 'SourceObjectCondition', 'SourceObjectDependency', 'StringLiteral', 'SystemDiagram', 'TargetObject', 'TargetObjectAttribute', 'WithCustomProperties']; } protected override computeIsSubtype(subtype: string, supertype: string): boolean { @@ -406,10 +442,10 @@ export class CrossModelAstReflection extends AbstractAstReflection { case EntityAttribute: case SourceObjectAttribute: case TargetObjectAttribute: { - return this.isSubtype(Attribute, supertype); + return this.isSubtype(Attribute, supertype) || this.isSubtype(WithCustomProperties, supertype); } case EntityNodeAttribute: { - return this.isSubtype(EntityAttribute, supertype); + return this.isSubtype(EntityAttribute, supertype) || this.isSubtype(WithCustomProperties, supertype); } case JoinCondition: { return this.isSubtype(SourceObjectCondition, supertype); @@ -468,6 +504,7 @@ export class CrossModelAstReflection extends AbstractAstReflection { return { name: 'AttributeMapping', mandatory: [ + { name: 'customProperties', type: 'array' }, { name: 'sources', type: 'array' } ] }; @@ -476,7 +513,16 @@ export class CrossModelAstReflection extends AbstractAstReflection { return { name: 'Entity', mandatory: [ - { name: 'attributes', type: 'array' } + { name: 'attributes', type: 'array' }, + { name: 'customProperties', type: 'array' } + ] + }; + } + case 'EntityNode': { + return { + name: 'EntityNode', + mandatory: [ + { name: 'customProperties', type: 'array' } ] }; } @@ -484,6 +530,7 @@ export class CrossModelAstReflection extends AbstractAstReflection { return { name: 'Mapping', mandatory: [ + { name: 'customProperties', type: 'array' }, { name: 'sources', type: 'array' } ] }; @@ -492,7 +539,24 @@ export class CrossModelAstReflection extends AbstractAstReflection { return { name: 'Relationship', mandatory: [ - { name: 'attributes', type: 'array' } + { name: 'attributes', type: 'array' }, + { name: 'customProperties', type: 'array' } + ] + }; + } + case 'RelationshipAttribute': { + return { + name: 'RelationshipAttribute', + mandatory: [ + { name: 'customProperties', type: 'array' } + ] + }; + } + case 'RelationshipEdge': { + return { + name: 'RelationshipEdge', + mandatory: [ + { name: 'customProperties', type: 'array' } ] }; } @@ -501,6 +565,7 @@ export class CrossModelAstReflection extends AbstractAstReflection { name: 'SourceObject', mandatory: [ { name: 'conditions', type: 'array' }, + { name: 'customProperties', type: 'array' }, { name: 'dependencies', type: 'array' } ] }; @@ -509,6 +574,7 @@ export class CrossModelAstReflection extends AbstractAstReflection { return { name: 'SystemDiagram', mandatory: [ + { name: 'customProperties', type: 'array' }, { name: 'edges', type: 'array' }, { name: 'nodes', type: 'array' } ] @@ -518,10 +584,19 @@ export class CrossModelAstReflection extends AbstractAstReflection { return { name: 'TargetObject', mandatory: [ + { name: 'customProperties', type: 'array' }, { name: 'mappings', type: 'array' } ] }; } + case 'WithCustomProperties': { + return { + name: 'WithCustomProperties', + mandatory: [ + { name: 'customProperties', type: 'array' } + ] + }; + } case 'EntityAttribute': { return { name: 'EntityAttribute', diff --git a/extensions/crossmodel-lang/src/language-server/generated/grammar.ts b/extensions/crossmodel-lang/src/language-server/generated/grammar.ts index 4fae890b..521110b4 100644 --- a/extensions/crossmodel-lang/src/language-server/generated/grammar.ts +++ b/extensions/crossmodel-lang/src/language-server/generated/grammar.ts @@ -39,7 +39,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@12" + "$ref": "#/rules@14" }, "arguments": [] } @@ -51,7 +51,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@14" + "$ref": "#/rules@16" }, "arguments": [] } @@ -63,7 +63,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@17" + "$ref": "#/rules@19" }, "arguments": [] } @@ -94,7 +94,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -113,7 +113,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@11" + "$ref": "#/rules@13" }, "arguments": [] } @@ -136,7 +136,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } @@ -162,7 +162,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } @@ -184,7 +184,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -213,7 +213,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@10" }, "arguments": [] } @@ -223,7 +223,15 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@4" + }, + "arguments": [], + "cardinality": "?" + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@10" }, "arguments": [] } @@ -260,7 +268,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@11" + "$ref": "#/rules@13" }, "arguments": [] } @@ -280,7 +288,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } @@ -300,7 +308,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } @@ -355,13 +363,21 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } } ], "cardinality": "?" + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@4" + }, + "arguments": [], + "cardinality": "?" } ] }, @@ -382,7 +398,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@11" + "$ref": "#/rules@13" }, "arguments": [] }, @@ -396,7 +412,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@11" + "$ref": "#/rules@13" }, "arguments": [] } @@ -412,6 +428,126 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "parameters": [], "wildcard": false }, + { + "$type": "ParserRule", + "name": "CustomProperties", + "fragment": true, + "definition": { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "customProperties" + }, + { + "$type": "Keyword", + "value": ":" + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@11" + }, + "arguments": [] + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "-" + }, + { + "$type": "Assignment", + "feature": "customProperties", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@5" + }, + "arguments": [] + } + } + ], + "cardinality": "*" + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@10" + }, + "arguments": [] + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, + { + "$type": "ParserRule", + "name": "CustomProperty", + "definition": { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "name" + }, + { + "$type": "Keyword", + "value": ":" + }, + { + "$type": "Assignment", + "feature": "name", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@13" + }, + "arguments": [] + } + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "value" + }, + { + "$type": "Keyword", + "value": ":" + }, + { + "$type": "Assignment", + "feature": "value", + "operator": "=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@6" + }, + "arguments": [] + } + } + ], + "cardinality": "?" + } + ] + }, + "definesHiddenTokens": false, + "entry": false, + "fragment": false, + "hiddenTokens": [], + "parameters": [], + "wildcard": false + }, { "$type": "TerminalRule", "name": "STRING", @@ -525,7 +661,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -544,7 +680,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@11" + "$ref": "#/rules@13" }, "arguments": [] } @@ -567,7 +703,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } @@ -593,7 +729,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } @@ -670,7 +806,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } @@ -689,7 +825,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -707,7 +843,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@13" + "$ref": "#/rules@15" }, "arguments": [] } @@ -718,7 +854,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@10" }, "arguments": [] } @@ -728,7 +864,15 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@4" + }, + "arguments": [], + "cardinality": "?" + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@10" }, "arguments": [] } @@ -800,6 +944,14 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load }, "deprecatedSyntax": false } + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@4" + }, + "arguments": [], + "cardinality": "?" } ] }, @@ -839,7 +991,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -858,7 +1010,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@11" + "$ref": "#/rules@13" }, "arguments": [] } @@ -881,7 +1033,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } @@ -907,7 +1059,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } @@ -929,7 +1081,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -947,7 +1099,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@15" + "$ref": "#/rules@17" }, "arguments": [] } @@ -958,7 +1110,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@10" }, "arguments": [] } @@ -979,7 +1131,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -997,7 +1149,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@16" + "$ref": "#/rules@18" }, "arguments": [] } @@ -1008,7 +1160,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@10" }, "arguments": [] } @@ -1018,7 +1170,15 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@4" + }, + "arguments": [], + "cardinality": "?" + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@10" }, "arguments": [] } @@ -1055,7 +1215,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@11" + "$ref": "#/rules@13" }, "arguments": [] } @@ -1078,7 +1238,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } @@ -1104,7 +1264,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } @@ -1154,7 +1314,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@5" + "$ref": "#/rules@7" }, "arguments": [] } @@ -1174,7 +1334,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@5" + "$ref": "#/rules@7" }, "arguments": [] } @@ -1194,7 +1354,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@5" + "$ref": "#/rules@7" }, "arguments": [] } @@ -1214,10 +1374,18 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@5" + "$ref": "#/rules@7" }, "arguments": [] } + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@4" + }, + "arguments": [], + "cardinality": "?" } ] }, @@ -1249,7 +1417,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@11" + "$ref": "#/rules@13" }, "arguments": [] } @@ -1269,7 +1437,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@12" + "$ref": "#/rules@14" }, "terminal": { "$type": "RuleCall", @@ -1296,7 +1464,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@15" + "$ref": "#/rules@17" }, "terminal": { "$type": "RuleCall", @@ -1323,7 +1491,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@15" + "$ref": "#/rules@17" }, "terminal": { "$type": "RuleCall", @@ -1334,6 +1502,14 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load }, "deprecatedSyntax": false } + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@4" + }, + "arguments": [], + "cardinality": "?" } ] }, @@ -1361,7 +1537,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -1380,7 +1556,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@11" + "$ref": "#/rules@13" }, "arguments": [] } @@ -1399,7 +1575,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -1417,7 +1593,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@18" + "$ref": "#/rules@20" }, "arguments": [] } @@ -1428,7 +1604,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@10" }, "arguments": [] } @@ -1450,7 +1626,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@25" + "$ref": "#/rules@27" }, "arguments": [] } @@ -1458,7 +1634,15 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@4" + }, + "arguments": [], + "cardinality": "?" + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@10" }, "arguments": [] } @@ -1492,7 +1676,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@11" + "$ref": "#/rules@13" }, "arguments": [] } @@ -1539,7 +1723,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@19" + "$ref": "#/rules@21" }, "arguments": [] } @@ -1558,7 +1742,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -1576,7 +1760,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@20" + "$ref": "#/rules@22" }, "arguments": [] } @@ -1587,7 +1771,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@10" }, "arguments": [] } @@ -1608,7 +1792,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -1626,7 +1810,57 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@21" + "$ref": "#/rules@23" + }, + "arguments": [] + } + } + ], + "cardinality": "*" + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@10" + }, + "arguments": [] + } + ], + "cardinality": "?" + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "customProperties" + }, + { + "$type": "Keyword", + "value": ":" + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@11" + }, + "arguments": [] + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "-" + }, + { + "$type": "Assignment", + "feature": "customProperties", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@5" }, "arguments": [] } @@ -1637,7 +1871,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@10" }, "arguments": [] } @@ -1699,7 +1933,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/rules@18" + "$ref": "#/rules@20" }, "terminal": { "$type": "RuleCall", @@ -1724,7 +1958,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "definition": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@22" + "$ref": "#/rules@24" }, "arguments": [] }, @@ -1745,7 +1979,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@23" + "$ref": "#/rules@25" }, "arguments": [] } @@ -1770,7 +2004,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@24" + "$ref": "#/rules@26" }, "arguments": [] } @@ -1816,7 +2050,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@24" + "$ref": "#/rules@26" }, "arguments": [] } @@ -1857,7 +2091,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@5" + "$ref": "#/rules@7" }, "arguments": [] } @@ -1881,7 +2115,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } @@ -1905,7 +2139,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/interfaces@3" + "$ref": "#/interfaces@4" }, "terminal": { "$type": "RuleCall", @@ -1937,7 +2171,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -1982,7 +2216,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -2000,7 +2234,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@26" + "$ref": "#/rules@28" }, "arguments": [] } @@ -2011,7 +2245,57 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@10" + }, + "arguments": [] + } + ], + "cardinality": "?" + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "customProperties" + }, + { + "$type": "Keyword", + "value": ":" + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@11" + }, + "arguments": [] + }, + { + "$type": "Group", + "elements": [ + { + "$type": "Keyword", + "value": "-" + }, + { + "$type": "Assignment", + "feature": "customProperties", + "operator": "+=", + "terminal": { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@5" + }, + "arguments": [] + } + } + ], + "cardinality": "*" + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@10" }, "arguments": [] } @@ -2021,7 +2305,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@10" }, "arguments": [] } @@ -2055,7 +2339,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@27" + "$ref": "#/rules@29" }, "arguments": [] } @@ -2074,7 +2358,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@9" + "$ref": "#/rules@11" }, "arguments": [] }, @@ -2092,7 +2376,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@28" + "$ref": "#/rules@30" }, "arguments": [] } @@ -2103,7 +2387,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load { "$type": "RuleCall", "rule": { - "$ref": "#/rules@8" + "$ref": "#/rules@10" }, "arguments": [] } @@ -2128,13 +2412,21 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "RuleCall", "rule": { - "$ref": "#/rules@4" + "$ref": "#/rules@6" }, "arguments": [] } } ], "cardinality": "?" + }, + { + "$type": "RuleCall", + "rule": { + "$ref": "#/rules@4" + }, + "arguments": [], + "cardinality": "?" } ] }, @@ -2155,7 +2447,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/interfaces@4" + "$ref": "#/interfaces@5" }, "terminal": { "$type": "RuleCall", @@ -2184,7 +2476,7 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "terminal": { "$type": "CrossReference", "type": { - "$ref": "#/interfaces@3" + "$ref": "#/interfaces@4" }, "terminal": { "$type": "RuleCall", @@ -2267,15 +2559,42 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "superTypes": [ { "$ref": "#/interfaces@0" + }, + { + "$ref": "#/interfaces@2" } ] }, + { + "$type": "Interface", + "attributes": [ + { + "$type": "TypeAttribute", + "name": "customProperties", + "type": { + "$type": "ArrayType", + "elementType": { + "$type": "SimpleType", + "typeRef": { + "$ref": "#/rules@5" + } + } + }, + "isOptional": false + } + ], + "name": "WithCustomProperties", + "superTypes": [] + }, { "$type": "Interface", "name": "EntityNodeAttribute", "superTypes": [ { "$ref": "#/interfaces@1" + }, + { + "$ref": "#/interfaces@2" } ], "attributes": [] @@ -2286,6 +2605,9 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "superTypes": [ { "$ref": "#/interfaces@0" + }, + { + "$ref": "#/interfaces@2" } ], "attributes": [] @@ -2296,6 +2618,9 @@ export const CrossModelGrammar = (): Grammar => loadedCrossModelGrammar ?? (load "superTypes": [ { "$ref": "#/interfaces@0" + }, + { + "$ref": "#/interfaces@2" } ], "attributes": [] diff --git a/extensions/crossmodel-lang/src/language-server/mapping.langium b/extensions/crossmodel-lang/src/language-server/mapping.langium index 3849ea97..a1953c67 100644 --- a/extensions/crossmodel-lang/src/language-server/mapping.langium +++ b/extensions/crossmodel-lang/src/language-server/mapping.langium @@ -2,10 +2,10 @@ import 'terminals' import 'entity' import 'relationship' -interface SourceObjectAttribute extends Attribute { +interface SourceObjectAttribute extends Attribute, WithCustomProperties { } -interface TargetObjectAttribute extends Attribute { +interface TargetObjectAttribute extends Attribute, WithCustomProperties { } Mapping: @@ -18,6 +18,7 @@ Mapping: DEDENT )? 'target' ':' target=TargetObject + CustomProperties? DEDENT ; @@ -35,6 +36,12 @@ SourceObject: ('-' conditions+=SourceObjectCondition)* DEDENT )? + // for some reason using the CustomProperties fragment here causes parsing issues in the language server + ('customProperties' ':' + INDENT + ('-' customProperties+=CustomProperty)* + DEDENT + )? ; JoinType returns string: 'from' | 'inner-join' | 'cross-join' | 'left-join' | 'apply'; @@ -67,6 +74,12 @@ TargetObject: ('-' mappings+=AttributeMapping)+ DEDENT )? + // for some reason using the CustomProperties fragment here causes parsing issues in the language server + ('customProperties' ':' + INDENT + ('-' customProperties+=CustomProperty)* + DEDENT + )? DEDENT ; @@ -78,6 +91,7 @@ AttributeMapping: DEDENT )? ('expression' ':' expression=STRING)? + CustomProperties? ; AttributeMappingTarget: diff --git a/extensions/crossmodel-lang/src/language-server/relationship.langium b/extensions/crossmodel-lang/src/language-server/relationship.langium index be90bf52..77e6fe02 100644 --- a/extensions/crossmodel-lang/src/language-server/relationship.langium +++ b/extensions/crossmodel-lang/src/language-server/relationship.langium @@ -16,10 +16,12 @@ Relationship: ('-' attributes+=RelationshipAttribute)+ DEDENT )? + CustomProperties? DEDENT ; RelationshipAttribute: 'parent' ':' parent=[Attribute:IDReference] 'child' ':' child=[Attribute:IDReference] + CustomProperties? ; diff --git a/extensions/crossmodel-lang/src/language-server/system-diagram.langium b/extensions/crossmodel-lang/src/language-server/system-diagram.langium index 1aea2570..f49fa55b 100644 --- a/extensions/crossmodel-lang/src/language-server/system-diagram.langium +++ b/extensions/crossmodel-lang/src/language-server/system-diagram.langium @@ -20,6 +20,7 @@ SystemDiagram: ('-' edges+=RelationshipEdge)+ DEDENT )? + CustomProperties? DEDENT )* ; @@ -34,9 +35,10 @@ EntityNode: 'y' ':' y=NUMBER 'width' ':' width=NUMBER 'height' ':' height=NUMBER + CustomProperties? ; -interface EntityNodeAttribute extends EntityAttribute { +interface EntityNodeAttribute extends EntityAttribute, WithCustomProperties { } RelationshipEdge: @@ -44,4 +46,5 @@ RelationshipEdge: 'relationship' ':' relationship=[Relationship:IDReference] 'sourceNode' ':' sourceNode=[EntityNode:IDReference] 'targetNode' ':' targetNode=[EntityNode:IDReference] + CustomProperties? ; \ No newline at end of file diff --git a/extensions/crossmodel-lang/src/language-server/terminals.langium b/extensions/crossmodel-lang/src/language-server/terminals.langium index 22d67340..13f9a6c6 100644 --- a/extensions/crossmodel-lang/src/language-server/terminals.langium +++ b/extensions/crossmodel-lang/src/language-server/terminals.langium @@ -1,8 +1,25 @@ +// COMMON // Identification for cross references IDReference returns string: ID ('.' ID)*; +// Custom properties +interface WithCustomProperties { + customProperties: CustomProperty[]; +} + +fragment CustomProperties: + 'customProperties' ':' + INDENT + ('-' customProperties+=CustomProperty)* + DEDENT + ; + +CustomProperty: + 'name' ':' name=ID + ('value' ':' value=STRING)?; + // Scalar values terminal STRING: /"[^"]*"|'[^']*'/; terminal NUMBER returns number: /(-)?[0-9]+(\.[0-9]*)?/; diff --git a/extensions/crossmodel-lang/src/language-server/util/ast-util.ts b/extensions/crossmodel-lang/src/language-server/util/ast-util.ts index ebe1e01d..949293fe 100644 --- a/extensions/crossmodel-lang/src/language-server/util/ast-util.ts +++ b/extensions/crossmodel-lang/src/language-server/util/ast-util.ts @@ -100,7 +100,8 @@ export function createSourceObject(entity: Entity | AstNodeDescription, containe entity: { $refText, ref }, join: 'from', dependencies: [], - conditions: [] + conditions: [], + customProperties: [] }; } @@ -143,7 +144,20 @@ export function findDocument(node?: AstNode): Langi return result ? >result : undefined; } -export function fixDocument(node: T | undefined, document: LangiumDocument | undefined): T | undefined { +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function fixDocument( + node: undefined, + document: LangiumDocument | undefined +): undefined; +export function fixDocument(node: T, document: LangiumDocument | undefined): T; +export function fixDocument( + node: T | undefined, + document: LangiumDocument | undefined +): T | undefined; +export function fixDocument( + node: T | undefined, + document: LangiumDocument | undefined +): T | undefined { if (!node || !document) { return node; } diff --git a/extensions/crossmodel-lang/syntaxes/cross-model.tmLanguage.json b/extensions/crossmodel-lang/syntaxes/cross-model.tmLanguage.json index 472fac31..d22f28e6 100644 --- a/extensions/crossmodel-lang/syntaxes/cross-model.tmLanguage.json +++ b/extensions/crossmodel-lang/syntaxes/cross-model.tmLanguage.json @@ -10,7 +10,7 @@ }, { "name": "keyword.control.cross-model", - "match": "\\b(TRUE|apply|attribute|attributes|child|conditions|cross-join|datatype|dependencies|description|diagram|edges|entity|expression|from|height|id|identifier|inner-join|join|left-join|mapping|mappings|name|nodes|parent|relationship|sourceNode|sources|systemDiagram|target|targetNode|true|type|width|x|y)\\b" + "match": "\\b(TRUE|apply|attribute|attributes|child|conditions|cross-join|customProperties|datatype|dependencies|description|diagram|edges|entity|expression|from|height|id|identifier|inner-join|join|left-join|mapping|mappings|name|nodes|parent|relationship|sourceNode|sources|systemDiagram|target|targetNode|true|type|value|width|x|y)\\b" }, { "name": "string.quoted.double.cross-model", diff --git a/extensions/crossmodel-lang/test/language-server/serializer/cross-model-serializer.test.ts b/extensions/crossmodel-lang/test/language-server/serializer/cross-model-serializer.test.ts index b082dcdc..82110f8b 100644 --- a/extensions/crossmodel-lang/test/language-server/serializer/cross-model-serializer.test.ts +++ b/extensions/crossmodel-lang/test/language-server/serializer/cross-model-serializer.test.ts @@ -34,7 +34,8 @@ describe('CrossModelLexer', () => { description: 'Test description', id: 'testId', name: 'test Name', - attributes: [] + attributes: [], + customProperties: [] }; crossModelRootWithoutAttributes = _.cloneDeep(crossModelRoot); @@ -46,7 +47,8 @@ describe('CrossModelLexer', () => { $type: 'EntityAttribute', id: 'Attribute1', name: 'Attribute1', - datatype: 'Datatype Attribute 1' + datatype: 'Datatype Attribute 1', + customProperties: [] }, { identifier: false, @@ -54,7 +56,8 @@ describe('CrossModelLexer', () => { $type: 'EntityAttribute', id: 'Attribute2', name: 'Attribute2', - datatype: 'Datatype Attribute 2' + datatype: 'Datatype Attribute 2', + customProperties: [] } ]; @@ -64,7 +67,8 @@ describe('CrossModelLexer', () => { description: 'Test description', attributes: [], id: 'testId', - name: 'test Name' + name: 'test Name', + customProperties: [] }; crossModelRootWithAttributesDifPlace.entity.attributes = [ { @@ -73,7 +77,8 @@ describe('CrossModelLexer', () => { $type: 'EntityAttribute', id: 'Attribute1', name: 'Attribute1', - datatype: 'Datatype Attribute 1' + datatype: 'Datatype Attribute 1', + customProperties: [] }, { identifier: false, @@ -81,7 +86,8 @@ describe('CrossModelLexer', () => { $type: 'EntityAttribute', id: 'Attribute2', name: 'Attribute2', - datatype: 'Datatype Attribute 2' + datatype: 'Datatype Attribute 2', + customProperties: [] } ]; }); @@ -120,7 +126,8 @@ describe('CrossModelLexer', () => { description: 'Test description', attributes: [], id: 'Ref1', - name: 'test Name' + name: 'test Name', + customProperties: [] } }; @@ -132,7 +139,8 @@ describe('CrossModelLexer', () => { description: 'Test description', attributes: [], id: 'Ref2', - name: 'test Name' + name: 'test Name', + customProperties: [] } }; @@ -145,7 +153,8 @@ describe('CrossModelLexer', () => { parent: ref1, child: ref2, type: 'n:m', - attributes: [] + attributes: [], + customProperties: [] }; }); @@ -171,7 +180,8 @@ describe('CrossModelLexer', () => { description: 'Test description', attributes: [], id: 'Ref1', - name: 'test Name' + name: 'test Name', + customProperties: [] } }; @@ -183,7 +193,8 @@ describe('CrossModelLexer', () => { description: 'Test description', attributes: [], id: 'Ref2', - name: 'test Name' + name: 'test Name', + customProperties: [] } }; @@ -198,7 +209,8 @@ describe('CrossModelLexer', () => { parent: ref1, child: ref2, type: 'n:m', - attributes: [] + attributes: [], + customProperties: [] } }; @@ -209,7 +221,8 @@ describe('CrossModelLexer', () => { id: 'testId', name: 'test Name', nodes: [], - edges: [] + edges: [], + customProperties: [] }; crossModelRoot.systemDiagram.nodes = [ @@ -222,7 +235,8 @@ describe('CrossModelLexer', () => { height: 102, entity: ref1, id: 'Node1', - name: 'Node 1' + name: 'Node 1', + customProperties: [] }, { $container: crossModelRoot.systemDiagram, @@ -233,7 +247,8 @@ describe('CrossModelLexer', () => { height: 102, entity: ref2, id: 'Node2', - name: 'Node 2' + name: 'Node 2', + customProperties: [] } ]; @@ -244,7 +259,8 @@ describe('CrossModelLexer', () => { relationship: ref3, id: 'Edge1', sourceNode: { $refText: 'A' }, - targetNode: { $refText: 'B' } + targetNode: { $refText: 'B' }, + customProperties: [] } ]; }); diff --git a/packages/protocol/src/model-service/protocol.ts b/packages/protocol/src/model-service/protocol.ts index f2a996e4..3c7c061b 100644 --- a/packages/protocol/src/model-service/protocol.ts +++ b/packages/protocol/src/model-service/protocol.ts @@ -26,6 +26,15 @@ export interface Identifiable { $globalId: string; } +export interface WithCustomProperties { + customProperties?: Array; +} + +export interface CustomProperty { + name: string; + value?: string; +} + export interface CrossModelRoot extends CrossModelElement { readonly $type: 'CrossModelRoot'; entity?: Entity; @@ -38,7 +47,7 @@ export function isCrossModelRoot(model?: any): model is CrossModelRoot { } export const EntityType = 'Entity'; -export interface Entity extends CrossModelElement, Identifiable { +export interface Entity extends CrossModelElement, Identifiable, WithCustomProperties { readonly $type: typeof EntityType; attributes: Array; description?: string; @@ -52,13 +61,13 @@ export interface Attribute extends CrossModelElement, Identifiable { } export const EntityAttributeType = 'EntityAttribute'; -export interface EntityAttribute extends Attribute { +export interface EntityAttribute extends Attribute, WithCustomProperties { readonly $type: typeof EntityAttributeType; identifier?: boolean; } export const RelationshipType = 'Relationship'; -export interface Relationship extends CrossModelElement, Identifiable { +export interface Relationship extends CrossModelElement, Identifiable, WithCustomProperties { readonly $type: typeof RelationshipType; attributes: Array; child?: Reference; @@ -69,14 +78,14 @@ export interface Relationship extends CrossModelElement, Identifiable { } export const RelationshipAttributeType = 'RelationshipAttribute'; -export interface RelationshipAttribute extends CrossModelElement { +export interface RelationshipAttribute extends CrossModelElement, WithCustomProperties { readonly $type: typeof RelationshipAttributeType; parent?: Reference; child?: Reference; } export const MappingType = 'Mapping'; -export interface Mapping extends CrossModelElement, Identifiable { +export interface Mapping extends CrossModelElement, Identifiable, WithCustomProperties { readonly $type: typeof MappingType; sources: Array; target: TargetObject; @@ -84,7 +93,7 @@ export interface Mapping extends CrossModelElement, Identifiable { export const SourceObjectType = 'SourceObject'; export type SourceObjectJoinType = 'from' | 'inner-join' | 'cross-join' | 'left-join' | 'apply'; -export interface SourceObject extends CrossModelElement, Identifiable { +export interface SourceObject extends CrossModelElement, Identifiable, WithCustomProperties { readonly $type: typeof SourceObjectType; entity?: Reference; join?: SourceObjectJoinType; @@ -135,14 +144,14 @@ export interface SourceObjectAttributeReference extends CrossModelElement { } export const TargetObjectType = 'TargetObject'; -export interface TargetObject extends CrossModelElement { +export interface TargetObject extends CrossModelElement, WithCustomProperties { readonly $type: typeof TargetObjectType; entity?: Reference; mappings: Array; } export const AttributeMappingType = 'AttributeMapping'; -export interface AttributeMapping extends CrossModelElement { +export interface AttributeMapping extends CrossModelElement, WithCustomProperties { readonly $type: typeof AttributeMappingType; attribute?: AttributeMappingTarget; sources: Array; @@ -156,7 +165,7 @@ export interface AttributeMappingTarget extends CrossModelElement { } export const TargetObjectAttributeType = 'TargetObjectAttribute'; -export interface TargetObjectAttribute extends Attribute { +export interface TargetObjectAttribute extends Attribute, WithCustomProperties { readonly $type: typeof TargetObjectAttributeType; } @@ -167,7 +176,7 @@ export interface AttributeMappingSource extends CrossModelElement { } export const SourceObjectAttributeType = 'SourceObjectAttribute'; -export interface SourceObjectAttribute extends Attribute { +export interface SourceObjectAttribute extends Attribute, WithCustomProperties { readonly $type: typeof SourceObjectAttributeType; }