From abe0fc18a91ee0022e7d783472dcd23ba6cf35d5 Mon Sep 17 00:00:00 2001 From: Anton Date: Wed, 8 Nov 2023 14:04:48 +0100 Subject: [PATCH 01/48] WIP refactor automaton --- src/lib/classes/automaton.ts | 68 +-- src/lib/classes/automaton/AutomatonClass.ts | 15 + src/lib/classes/automaton/Backend.ts | 2 +- src/lib/classes/automaton/Component.ts | 135 ----- .../classes/automaton/ComponentInstance.ts | 29 -- src/lib/classes/automaton/Declaration.ts | 41 -- src/lib/classes/automaton/DeclarationType.ts | 4 - src/lib/classes/automaton/Declarations.ts | 10 + src/lib/classes/automaton/Dimensions.ts | 40 ++ src/lib/classes/automaton/Edge.ts | 162 ------ .../classes/automaton/GlobalDeclarations.ts | 31 ++ src/lib/classes/automaton/HasId.ts | 6 + src/lib/classes/automaton/Id.ts | 75 +++ src/lib/classes/automaton/IdMap.ts | 473 ++++++++++++++++++ src/lib/classes/automaton/IdScopedMap.ts | 163 ++++++ src/lib/classes/automaton/IdSubsetMap.ts | 143 ++++++ src/lib/classes/automaton/Invariant.ts | 19 - src/lib/classes/automaton/Location.ts | 107 ---- src/lib/classes/automaton/Locations.ts | 174 +++++++ src/lib/classes/automaton/Nail.ts | 22 - src/lib/classes/automaton/Named.ts | 9 - src/lib/classes/automaton/Nickname.ts | 21 - src/lib/classes/automaton/Operator.ts | 34 -- src/lib/classes/automaton/Position.ts | 40 ++ src/lib/classes/automaton/Project.ts | 83 +++ src/lib/classes/automaton/ProjectId.ts | 36 ++ src/lib/classes/automaton/Projects.ts | 49 ++ src/lib/classes/automaton/Property.ts | 25 - src/lib/classes/automaton/Query.ts | 131 ++--- src/lib/classes/automaton/RelativePosition.ts | 78 +++ src/lib/classes/automaton/System.ts | 159 ------ .../classes/automaton/SystemDeclarations.ts | 31 ++ src/lib/classes/automaton/SystemEdge.ts | 19 - .../classes/automaton/component/Component.ts | 175 +++++++ .../automaton/component/ComponentId.ts | 8 + .../classes/automaton/component/Components.ts | 43 ++ .../classes/automaton/component/Invariant.ts | 51 ++ .../classes/automaton/component/Location.ts | 177 +++++++ .../automaton/component/LocationEdge.ts | 119 +++++ .../automaton/component/LocationEdgeId.ts | 33 ++ .../automaton/component/LocationEdgeNail.ts | 50 ++ .../automaton/component/LocationEdgeStatus.ts | 9 + .../automaton/component/LocationEdges.ts | 65 +++ .../classes/automaton/component/LocationId.ts | 87 ++++ .../automaton/component/LocationType.ts | 19 + .../automaton/component/LocationUrgency.ts | 25 + .../classes/automaton/component/Locations.ts | 45 ++ .../automaton/component/LocationsSubset.ts | 39 ++ src/lib/classes/automaton/component/Nail.ts | 48 ++ .../automaton/component/NailProperty.ts | 48 ++ .../automaton/component/NailPropertyType.ts | 30 ++ .../classes/automaton/component/Nickname.ts | 59 +++ .../classes/automaton/component/Property.ts | 48 ++ .../automaton/{ => component}/PropertyType.ts | 12 +- .../automaton/{ => component}/Status.ts | 2 +- .../automaton/{ => component}/Urgency.ts | 10 +- .../automaton/component/raw/RawComponent.ts | 25 + .../raw/RawComponentInstance.ts | 6 +- .../automaton/component/raw/RawComponents.ts | 12 + .../automaton/component/raw/RawInvariant.ts | 13 + .../automaton/component/raw/RawLocation.ts | 24 + .../component/raw/RawLocationEdge.ts | 25 + .../component/raw/RawLocationEdges.ts | 12 + .../raw/RawLocationType.ts} | 14 +- .../automaton/component/raw/RawLocations.ts | 12 + .../automaton/component/raw/RawNail.ts | 13 + .../automaton/component/raw/RawNickname.ts | 13 + .../automaton/component/raw/RawProperty.ts | 14 + src/lib/classes/automaton/id/HasId.ts | 5 + src/lib/classes/automaton/id/Id.ts | 16 + src/lib/classes/automaton/id/IdMap.ts | 178 +++++++ src/lib/classes/automaton/id/LocationId.ts | 17 + src/lib/classes/automaton/id/raw/RawId.ts | 13 + src/lib/classes/automaton/raw.ts | 56 --- src/lib/classes/automaton/raw/RawComponent.ts | 25 - .../{RawDeclaration.ts => RawDeclarations.ts} | 10 +- .../classes/automaton/raw/RawDimensions.ts | 12 + src/lib/classes/automaton/raw/RawEdge.ts | 25 - .../automaton/raw/RawGlobalDeclarations.ts | 14 + src/lib/classes/automaton/raw/RawId.ts | 15 + src/lib/classes/automaton/raw/RawLocation.ts | 26 - src/lib/classes/automaton/raw/RawNail.ts | 15 - src/lib/classes/automaton/raw/RawNumber.ts | 8 + src/lib/classes/automaton/raw/RawPosition.ts | 12 + src/lib/classes/automaton/raw/RawProject.ts | 23 + src/lib/classes/automaton/raw/RawProjects.ts | 12 + src/lib/classes/automaton/raw/RawQuery.ts | 15 +- .../automaton/raw/RawRelativePosition.ts | 12 + .../automaton/raw/RawSystemDeclarations.ts | 14 + .../classes/automaton/raw/RawSystemEdge.ts | 11 - src/lib/classes/automaton/raw/utils.ts | 66 +++ .../classes/automaton/system/ComponentEdge.ts | 42 ++ .../automaton/system/ComponentInstance.ts | 60 +++ .../automaton/system/ComponentInstances.ts | 66 +++ src/lib/classes/automaton/system/Operator.ts | 53 ++ .../automaton/{ => system}/OperatorType.ts | 2 +- src/lib/classes/automaton/system/Operators.ts | 62 +++ src/lib/classes/automaton/system/System.ts | 192 +++++++ src/lib/classes/automaton/system/SystemId.ts | 8 + .../classes/automaton/system/SystemMember.ts | 10 + .../automaton/system/SystemMemberEdge.ts | 43 ++ .../automaton/system/SystemMemberId.ts | 10 + .../classes/automaton/system/SystemMembers.ts | 40 ++ src/lib/classes/automaton/system/Systems.ts | 40 ++ .../automaton/system/raw/RawComponentEdge.ts | 11 + .../system/raw/RawComponentInstance.ts | 13 + .../system/raw/RawComponentInstances.ts | 12 + .../automaton/{ => system}/raw/RawOperator.ts | 6 +- .../automaton/system/raw/RawOperators.ts | 12 + .../automaton/{ => system}/raw/RawSystem.ts | 18 +- .../automaton/system/raw/RawSystemMembers.ts | 18 + .../automaton/system/raw/RawSystems.ts | 12 + src/lib/classes/draw.ts | 3 - src/lib/classes/draw/Dimensions.ts | 12 - src/lib/classes/draw/Point.ts | 30 -- src/lib/classes/fileAdapter/FileAdapter.ts | 2 +- src/lib/classes/project/Project.ts | 80 --- src/lib/components/contextMenu/contextMenu.ts | 4 +- .../components/overlayMenu/OverlayMenu.svelte | 4 +- src/lib/globalState/activeProject.ts | 2 +- 120 files changed, 4026 insertions(+), 1260 deletions(-) create mode 100644 src/lib/classes/automaton/AutomatonClass.ts delete mode 100644 src/lib/classes/automaton/Component.ts delete mode 100644 src/lib/classes/automaton/ComponentInstance.ts delete mode 100644 src/lib/classes/automaton/Declaration.ts delete mode 100644 src/lib/classes/automaton/DeclarationType.ts create mode 100644 src/lib/classes/automaton/Declarations.ts create mode 100644 src/lib/classes/automaton/Dimensions.ts delete mode 100644 src/lib/classes/automaton/Edge.ts create mode 100644 src/lib/classes/automaton/GlobalDeclarations.ts create mode 100644 src/lib/classes/automaton/HasId.ts create mode 100644 src/lib/classes/automaton/Id.ts create mode 100644 src/lib/classes/automaton/IdMap.ts create mode 100644 src/lib/classes/automaton/IdScopedMap.ts create mode 100644 src/lib/classes/automaton/IdSubsetMap.ts delete mode 100644 src/lib/classes/automaton/Invariant.ts delete mode 100644 src/lib/classes/automaton/Location.ts create mode 100644 src/lib/classes/automaton/Locations.ts delete mode 100644 src/lib/classes/automaton/Nail.ts delete mode 100644 src/lib/classes/automaton/Named.ts delete mode 100644 src/lib/classes/automaton/Nickname.ts delete mode 100644 src/lib/classes/automaton/Operator.ts create mode 100644 src/lib/classes/automaton/Position.ts create mode 100644 src/lib/classes/automaton/Project.ts create mode 100644 src/lib/classes/automaton/ProjectId.ts create mode 100644 src/lib/classes/automaton/Projects.ts delete mode 100644 src/lib/classes/automaton/Property.ts create mode 100644 src/lib/classes/automaton/RelativePosition.ts delete mode 100644 src/lib/classes/automaton/System.ts create mode 100644 src/lib/classes/automaton/SystemDeclarations.ts delete mode 100644 src/lib/classes/automaton/SystemEdge.ts create mode 100644 src/lib/classes/automaton/component/Component.ts create mode 100644 src/lib/classes/automaton/component/ComponentId.ts create mode 100644 src/lib/classes/automaton/component/Components.ts create mode 100644 src/lib/classes/automaton/component/Invariant.ts create mode 100644 src/lib/classes/automaton/component/Location.ts create mode 100644 src/lib/classes/automaton/component/LocationEdge.ts create mode 100644 src/lib/classes/automaton/component/LocationEdgeId.ts create mode 100644 src/lib/classes/automaton/component/LocationEdgeNail.ts create mode 100644 src/lib/classes/automaton/component/LocationEdgeStatus.ts create mode 100644 src/lib/classes/automaton/component/LocationEdges.ts create mode 100644 src/lib/classes/automaton/component/LocationId.ts create mode 100644 src/lib/classes/automaton/component/LocationType.ts create mode 100644 src/lib/classes/automaton/component/LocationUrgency.ts create mode 100644 src/lib/classes/automaton/component/Locations.ts create mode 100644 src/lib/classes/automaton/component/LocationsSubset.ts create mode 100644 src/lib/classes/automaton/component/Nail.ts create mode 100644 src/lib/classes/automaton/component/NailProperty.ts create mode 100644 src/lib/classes/automaton/component/NailPropertyType.ts create mode 100644 src/lib/classes/automaton/component/Nickname.ts create mode 100644 src/lib/classes/automaton/component/Property.ts rename src/lib/classes/automaton/{ => component}/PropertyType.ts (91%) rename src/lib/classes/automaton/{ => component}/Status.ts (95%) rename src/lib/classes/automaton/{ => component}/Urgency.ts (90%) create mode 100644 src/lib/classes/automaton/component/raw/RawComponent.ts rename src/lib/classes/automaton/{ => component}/raw/RawComponentInstance.ts (75%) create mode 100644 src/lib/classes/automaton/component/raw/RawComponents.ts create mode 100644 src/lib/classes/automaton/component/raw/RawInvariant.ts create mode 100644 src/lib/classes/automaton/component/raw/RawLocation.ts create mode 100644 src/lib/classes/automaton/component/raw/RawLocationEdge.ts create mode 100644 src/lib/classes/automaton/component/raw/RawLocationEdges.ts rename src/lib/classes/automaton/{LocationType.ts => component/raw/RawLocationType.ts} (87%) create mode 100644 src/lib/classes/automaton/component/raw/RawLocations.ts create mode 100644 src/lib/classes/automaton/component/raw/RawNail.ts create mode 100644 src/lib/classes/automaton/component/raw/RawNickname.ts create mode 100644 src/lib/classes/automaton/component/raw/RawProperty.ts create mode 100644 src/lib/classes/automaton/id/HasId.ts create mode 100644 src/lib/classes/automaton/id/Id.ts create mode 100644 src/lib/classes/automaton/id/IdMap.ts create mode 100644 src/lib/classes/automaton/id/LocationId.ts create mode 100644 src/lib/classes/automaton/id/raw/RawId.ts delete mode 100644 src/lib/classes/automaton/raw.ts delete mode 100644 src/lib/classes/automaton/raw/RawComponent.ts rename src/lib/classes/automaton/raw/{RawDeclaration.ts => RawDeclarations.ts} (62%) create mode 100644 src/lib/classes/automaton/raw/RawDimensions.ts delete mode 100644 src/lib/classes/automaton/raw/RawEdge.ts create mode 100644 src/lib/classes/automaton/raw/RawGlobalDeclarations.ts create mode 100644 src/lib/classes/automaton/raw/RawId.ts delete mode 100644 src/lib/classes/automaton/raw/RawLocation.ts delete mode 100644 src/lib/classes/automaton/raw/RawNail.ts create mode 100644 src/lib/classes/automaton/raw/RawNumber.ts create mode 100644 src/lib/classes/automaton/raw/RawPosition.ts create mode 100644 src/lib/classes/automaton/raw/RawProject.ts create mode 100644 src/lib/classes/automaton/raw/RawProjects.ts create mode 100644 src/lib/classes/automaton/raw/RawRelativePosition.ts create mode 100644 src/lib/classes/automaton/raw/RawSystemDeclarations.ts delete mode 100644 src/lib/classes/automaton/raw/RawSystemEdge.ts create mode 100644 src/lib/classes/automaton/raw/utils.ts create mode 100644 src/lib/classes/automaton/system/ComponentEdge.ts create mode 100644 src/lib/classes/automaton/system/ComponentInstance.ts create mode 100644 src/lib/classes/automaton/system/ComponentInstances.ts create mode 100644 src/lib/classes/automaton/system/Operator.ts rename src/lib/classes/automaton/{ => system}/OperatorType.ts (97%) create mode 100644 src/lib/classes/automaton/system/Operators.ts create mode 100644 src/lib/classes/automaton/system/System.ts create mode 100644 src/lib/classes/automaton/system/SystemId.ts create mode 100644 src/lib/classes/automaton/system/SystemMember.ts create mode 100644 src/lib/classes/automaton/system/SystemMemberEdge.ts create mode 100644 src/lib/classes/automaton/system/SystemMemberId.ts create mode 100644 src/lib/classes/automaton/system/SystemMembers.ts create mode 100644 src/lib/classes/automaton/system/Systems.ts create mode 100644 src/lib/classes/automaton/system/raw/RawComponentEdge.ts create mode 100644 src/lib/classes/automaton/system/raw/RawComponentInstance.ts create mode 100644 src/lib/classes/automaton/system/raw/RawComponentInstances.ts rename src/lib/classes/automaton/{ => system}/raw/RawOperator.ts (76%) create mode 100644 src/lib/classes/automaton/system/raw/RawOperators.ts rename src/lib/classes/automaton/{ => system}/raw/RawSystem.ts (62%) create mode 100644 src/lib/classes/automaton/system/raw/RawSystemMembers.ts create mode 100644 src/lib/classes/automaton/system/raw/RawSystems.ts delete mode 100644 src/lib/classes/draw.ts delete mode 100644 src/lib/classes/draw/Dimensions.ts delete mode 100644 src/lib/classes/draw/Point.ts delete mode 100644 src/lib/classes/project/Project.ts diff --git a/src/lib/classes/automaton.ts b/src/lib/classes/automaton.ts index c3de9ac0..ec96fb23 100644 --- a/src/lib/classes/automaton.ts +++ b/src/lib/classes/automaton.ts @@ -1,38 +1,40 @@ -// AUTOMATION TYPES -// Each of the Major Classes are what is the output of loading the ECDAR json files -// Each of the Minor Classes and Enums are helper classes to the Major Classes -// -// If a type is Raw, then that is the format of the ECDAR json files -// +/** + * AUTOMATON TYPES + * Each of the Major Classes are what is the output of loading the ECDAR json files + * Each of the Minor Classes and Enums are helper classes to the Major Classes + */ -// Raw Types -export * as Raw from "./automaton/raw"; +// Component classes +export { Component } from "./automaton/component/Component"; +export { LocationEdge } from "./automaton/component/LocationEdge"; +export { Location } from "./automaton/component/Location"; +export { Invariant } from "./automaton/component/Invariant"; +export { Nickname } from "./automaton/component/Nickname"; +export { NailProperty } from "./automaton/component/NailProperty"; +export { LocationEdgeNail } from "./automaton/component/LocationEdgeNail"; -// Enums -export { Backend } from "./automaton/Backend"; -export { DeclarationType } from "./automaton/DeclarationType"; -export { Status } from "./automaton/Status"; -export { LocationType } from "./automaton/LocationType"; -export { OperatorType } from "./automaton/OperatorType"; -export { PropertyType } from "./automaton/PropertyType"; -export { Urgency } from "./automaton/Urgency"; +// Component helpers +export { LocationEdgeStatus } from "./automaton/component/LocationEdgeStatus"; +export { LocationType } from "./automaton/component/LocationType"; +export { NailPropertyType } from "./automaton/component/NailPropertyType"; +export { LocationUrgency } from "./automaton/component/LocationUrgency"; + +// System classes +export { System } from "./automaton/system/System"; +export { ComponentInstance } from "./automaton/system/ComponentInstance"; +export { Operator } from "./automaton/system/Operator"; +export { SystemMemberEdge } from "./automaton/system/SystemMemberEdge"; -// Minor Classes -export { Invariant } from "./automaton/Invariant"; -export { Nickname } from "./automaton/Nickname"; -export { Operator } from "./automaton/Operator"; -export { Property } from "./automaton/Property"; -export { SystemEdge } from "./automaton/SystemEdge"; -export { ComponentInstance } from "./automaton/ComponentInstance"; -export { Nail } from "./automaton/Nail"; +// System helpers +export { OperatorType } from "./automaton/system/OperatorType"; -// Major Classes -export { Declaration } from "./automaton/Declaration"; -export { Component } from "./automaton/Component"; -export { Edge } from "./automaton/Edge"; -export { Location } from "./automaton/Location"; -export { Query, Queries } from "./automaton/Query"; -export { System } from "./automaton/System"; +// General Classes +export { SystemDeclarations } from "./automaton/SystemDeclarations"; +export { GlobalDeclarations } from "./automaton/GlobalDeclarations"; +export { Query } from "./automaton/Query"; +export { Project } from "./automaton/Project"; +export { Position } from "./automaton/Position"; +export { Dimensions } from "./automaton/Dimensions"; -// Interfaces -export type { Named } from "./automaton/Named"; +// General helpers +export { Backend } from "./automaton/Backend"; diff --git a/src/lib/classes/automaton/AutomatonClass.ts b/src/lib/classes/automaton/AutomatonClass.ts new file mode 100644 index 00000000..307249f1 --- /dev/null +++ b/src/lib/classes/automaton/AutomatonClass.ts @@ -0,0 +1,15 @@ +export abstract class AutomatonClass { + /** + * Converts the class to its raw counterpart + */ + abstract toRaw(): R; +} + +/** + * Should be part of the AutomatonClass. + * + * A static function that returns the class T from its raw counterpart R + */ +export type FromRaw = C extends undefined + ? (raw: R) => T + : (raw: R, references: C) => T; diff --git a/src/lib/classes/automaton/Backend.ts b/src/lib/classes/automaton/Backend.ts index 6a8cfaf8..87c84067 100644 --- a/src/lib/classes/automaton/Backend.ts +++ b/src/lib/classes/automaton/Backend.ts @@ -2,7 +2,7 @@ * What backend is targeted * - j-ECDAR or * - Reveaal - * */ + */ export enum Backend { J_ECDAR = 0, REVEAAL = 1, diff --git a/src/lib/classes/automaton/Component.ts b/src/lib/classes/automaton/Component.ts deleted file mode 100644 index b7f730bd..00000000 --- a/src/lib/classes/automaton/Component.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { Point, Dimensions } from "$lib/classes/draw"; - -import { Edge, Location, type Named, Raw } from "../automaton"; -import {} from "./raw/RawComponent"; - -/** - * # An Ecdar component - * It stores the edges and locations of a single automaton - * */ -export class Component - implements Raw.SerializeRaw, Raw.ToRaw, Named -{ - /** - * The name of the component - * */ - name: string = ""; - - /** - * The declarations of the component ex "clock t;" - * */ - declarations: string = ""; - - /** - * A list of Locations in the Component - * */ - locations: Location[] = []; - - /** - * A list of Edges in the Component - * */ - edges: Edge[] = []; - - /** - * A description of the Component - * */ - description: string = ""; - - /** - * The position of the Component - * */ - position = new Point(0, 0); - - /** - * The dimensions of the Component - * */ - dimensions: Dimensions; - - /** - * The color of the Component - * */ - color: string = "0"; - - /** - * Include in periodic checks - * ! Some more information might be needed ! - * */ - includeInPeriodicCheck: boolean = false; - - constructor( - name: string = "", - declarations: string = "", - locations: Location[] = [], - edges: Edge[] = [], - description: string = "", - position = new Point(0, 0), - dimensions = new Dimensions(100, 100), - color: string = "0", - includeInPeriodicCheck: boolean = false, - ) { - this.name = name; - this.declarations = declarations; - this.locations = locations; - this.edges = edges; - this.description = description; - this.position = position; - this.dimensions = dimensions; - this.color = color; - this.includeInPeriodicCheck = includeInPeriodicCheck; - } - - toRaw() { - return { - name: this.name, - declarations: this.declarations, - locations: this.locations.map((l) => { - return l.toRaw(); - }), - edges: this.edges.map((e) => { - return e.toRaw(); - }), - description: this.description, - x: this.position.x, - y: this.position.y, - width: this.dimensions.width, - height: this.dimensions.height, - color: this.color, - includeInPeriodicCheck: this.includeInPeriodicCheck, - }; - } - - serializeRaw() { - return JSON.stringify(this.toRaw()); - } - - /** - * Converts the Component into a RawComponent - * */ - static readonly fromRaw: Raw.FromRaw = ( - raw, - ) => { - return new Component( - raw.name, - raw.declarations, - raw.locations.map((raw) => { - return Location.fromRaw(raw); - }), - raw.edges.map((raw) => { - return Edge.fromRaw(raw); - }), - raw.description, - new Point(raw.x, raw.y), - new Dimensions(raw.width, raw.height), - raw.color, - raw.includeInPeriodicCheck, - ); - }; - - /** - * Creates a Component from a JSON string of a RawComponent - * */ - static readonly deserializeRaw: Raw.DeserializeRaw = (input) => { - const raw = Raw.parse(Raw.ZodRawComponent, input); - return Component.fromRaw(raw); - }; -} diff --git a/src/lib/classes/automaton/ComponentInstance.ts b/src/lib/classes/automaton/ComponentInstance.ts deleted file mode 100644 index c8f1ef64..00000000 --- a/src/lib/classes/automaton/ComponentInstance.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { Point } from "$lib/classes/draw"; - -/** - * # A Component Instance - * Is used by a System to store what components are used - * */ -export class ComponentInstance { - /** - * The id of the component instance - * This must be unique and is shared by Operators - * */ - id: number; - - /** - * The name of the component this references - * */ - name: string; - - /** - * The position of the component instance - * */ - position: Point; - - constructor(id: number, name: string, position: Point) { - this.id = id; - this.name = name; - this.position = position; - } -} diff --git a/src/lib/classes/automaton/Declaration.ts b/src/lib/classes/automaton/Declaration.ts deleted file mode 100644 index 7ac3ac1f..00000000 --- a/src/lib/classes/automaton/Declaration.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { Raw, DeclarationType } from "../automaton"; - -/** - * The top level declarations - * */ -export class Declaration - implements Raw.SerializeRaw, Raw.ToRaw -{ - type: DeclarationType; - declarations: string; - - constructor(type = DeclarationType.GLOBAL, declarations = "") { - this.type = type; - this.declarations = declarations; - } - - static readonly fromRaw: Raw.FromRaw = ( - raw, - ) => { - return new Declaration(raw.name as DeclarationType, raw.declarations); - }; - - static readonly deserializeRaw: Raw.DeserializeRaw = ( - input, - ) => { - const raw = Raw.parse(Raw.ZodRawDeclaration, input); - return Declaration.fromRaw(raw); - }; - - toRaw() { - return { - name: this.type, - declarations: this.declarations, - }; - } - - serializeRaw(): string { - const raw = this.toRaw(); - return JSON.stringify(raw); - } -} diff --git a/src/lib/classes/automaton/DeclarationType.ts b/src/lib/classes/automaton/DeclarationType.ts deleted file mode 100644 index ffe654d1..00000000 --- a/src/lib/classes/automaton/DeclarationType.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum DeclarationType { - GLOBAL = "Global Declarations", - SYSTEM = "System Declarations", -} diff --git a/src/lib/classes/automaton/Declarations.ts b/src/lib/classes/automaton/Declarations.ts new file mode 100644 index 00000000..7a58d291 --- /dev/null +++ b/src/lib/classes/automaton/Declarations.ts @@ -0,0 +1,10 @@ +import { AutomatonClass } from "./AutomatonClass"; + +/** + * Defines top level declarations, such as system declarations and global declarations + */ +export abstract class Declarations extends AutomatonClass { + constructor(public declarations: string = "") { + super(); + } +} diff --git a/src/lib/classes/automaton/Dimensions.ts b/src/lib/classes/automaton/Dimensions.ts new file mode 100644 index 00000000..eae5de03 --- /dev/null +++ b/src/lib/classes/automaton/Dimensions.ts @@ -0,0 +1,40 @@ +import { AutomatonClass, type FromRaw } from "./AutomatonClass"; +import type { RawDimensions } from "./raw/RawDimensions"; + +/** + * Describes the dimensions of something + */ +export class Dimensions extends AutomatonClass { + constructor( + /** + * The width + */ + public width: number, + + /** + * The height + */ + public height: number, + ) { + super(); + } + + /** + * Converts the Dimensions to a RawDimensions + */ + toRaw() { + return { + width: this.width, + height: this.height, + }; + } + + /** + * Converts a RawDimensions to a Dimensions + */ + static readonly fromRaw: FromRaw = ( + raw, + ) => { + return new Dimensions(raw.width, raw.height); + }; +} diff --git a/src/lib/classes/automaton/Edge.ts b/src/lib/classes/automaton/Edge.ts deleted file mode 100644 index 1235d746..00000000 --- a/src/lib/classes/automaton/Edge.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { Point } from "$lib/classes/draw"; - -import { Status, Property, PropertyType, Raw, type Nail } from "../automaton"; -import type { RawNail } from "./raw/RawNail"; - -/** - * # An Ecdar Edge - * Used to define edges in an Ecdar Component - * */ -export class Edge implements Raw.SerializeRaw, Raw.ToRaw { - /** - * The id of the edge - * - Must be "E" followed by a number and - * - Must be unique - * */ - id: string; - - /** - * # Unused - * */ - group: string; - - /** - * The id of the source location - * */ - sourceLocation: string; - - /** - * The id of the target location - * */ - targetLocation: string; - - /** - * The status of the edge - * - Input or - * - Output - * */ - status: Status; - - /** - * Unused - * */ - select: string; - - /** - * The guard of the edge - * ex "c <= 7" - * */ - guard: string; - - /** - * The update of the edge - * ex "c := 7" - * */ - update: string; - - /** - * The input OR output variable of the edge - * */ - sync: string; - - /** - * Unused - * */ - isLocked: boolean; - - /** - * The nails of the edge - * Modifies the path that the edge takes - * Defines properties on the edge - * */ - nails: Nail[]; - - constructor( - id: string = "", - group: string = "", - sourceLocation: string = "", - targetLocation: string = "", - status: Status = Status.INPUT, - select: string = "", - guard: string = "", - update: string = "", - sync: string = "", - isLocked: boolean = true, - nails: Nail[] = [], - ) { - this.id = id; - this.group = group; - this.sourceLocation = sourceLocation; - this.targetLocation = targetLocation; - this.status = status; - this.select = select; - this.guard = guard; - this.update = update; - this.sync = sync; - this.isLocked = isLocked; - this.nails = nails; - } - - toRaw() { - return { - id: this.id, - group: this.group, - sourceLocation: this.sourceLocation, - targetLocation: this.targetLocation, - status: this.status, - select: this.select, - guard: this.guard, - update: this.update, - sync: this.sync, - isLocked: this.isLocked, - nails: this.nails.reduce((res, c): RawNail[] => { - res.push({ - x: c.position.x, - y: c.position.y, - propertyType: c.property.type, - propertyX: c.property.position.x, - propertyY: c.property.position.y, - }); - return res; - }, []), - }; - } - serializeRaw() { - return JSON.stringify(this.toRaw()); - } - - /** - * Creates an Edge from a RawEdge - * */ - static readonly fromRaw: Raw.FromRaw = (raw) => { - return new Edge( - raw.id, - raw.group, - raw.sourceLocation, - raw.targetLocation, - raw.status as Status, - raw.select, - raw.guard, - raw.update, - raw.sync, - raw.isLocked, - raw.nails.map((nail) => { - return { - position: new Point(nail.x, nail.y), - property: new Property( - nail.propertyType as PropertyType, - new Point(nail.propertyX, nail.propertyY), - ), - }; - }), - ); - }; - - /** - * Creates an Edge from a JSON matching a RawEdge - * */ - static readonly deserializeRaw: Raw.DeserializeRaw = (input) => { - const raw = Raw.parse(Raw.ZodRawEdge, input); - return Edge.fromRaw(raw); - }; -} diff --git a/src/lib/classes/automaton/GlobalDeclarations.ts b/src/lib/classes/automaton/GlobalDeclarations.ts new file mode 100644 index 00000000..a7c70808 --- /dev/null +++ b/src/lib/classes/automaton/GlobalDeclarations.ts @@ -0,0 +1,31 @@ +import type { FromRaw } from "./AutomatonClass"; +import { Declarations } from "./Declarations"; +import type { RawGlobalDeclarations } from "./raw/RawGlobalDeclarations"; + +/** + * The top level global declarations + */ +export class GlobalDeclarations extends Declarations { + private id: "Global declarations" = "Global declarations"; + + /** + * Converts the GlobalDeclarations to a RawGlobalDeclarations + */ + toRaw() { + return { + name: this.id, + declarations: this.declarations, + }; + } + + /** + * Converts a RawGlobalDeclarations to a GlobalDeclarations + */ + static readonly fromRaw: FromRaw< + RawGlobalDeclarations, + undefined, + GlobalDeclarations + > = (raw) => { + return new GlobalDeclarations(raw.declarations); + }; +} diff --git a/src/lib/classes/automaton/HasId.ts b/src/lib/classes/automaton/HasId.ts new file mode 100644 index 00000000..a00e7946 --- /dev/null +++ b/src/lib/classes/automaton/HasId.ts @@ -0,0 +1,6 @@ +import type { Id } from "./Id"; +import type { RawId } from "./raw/RawId"; + +export interface HasId> { + id: I; +} diff --git a/src/lib/classes/automaton/Id.ts b/src/lib/classes/automaton/Id.ts new file mode 100644 index 00000000..1b53445a --- /dev/null +++ b/src/lib/classes/automaton/Id.ts @@ -0,0 +1,75 @@ +import type { RawId, RawStringId, RawNumberId } from "./raw/RawId"; +import { AutomatonClass } from "./AutomatonClass"; +import type { IIdMap } from "./IdMap"; + +export abstract class Id< + I, + R extends RawNumberId | RawStringId, +> extends AutomatonClass { + constructor(id: I | R) { + super(); + + const parsed = this.parse(id); + this.applyParse(parsed.rawId, parsed.order, parsed.orders); + } + + protected abstract parse(id: I | R): { + rawId: R; + order: R extends RawNumberId ? number : number | undefined; + orders: R extends RawNumberId ? undefined : number[] | undefined; + }; + + private applyParse( + rawId: R, + order: R extends RawNumberId ? number : number | undefined, + orders: R extends RawNumberId ? undefined : number[] | undefined, + ) { + this.#rawId = rawId; + this.#order = order; + this.#orders = orders; + + if (this.#orders === undefined) { + this.#higherOrder = undefined; + } else { + this.#higherOrder = [ + this.#orders.length, + this.#orders.reduce((total, next) => total + next, 0), + ]; + } + } + + #rawId!: R; + get rawId() { + return this.#rawId; + } + + #order!: R extends RawNumberId ? number : number | undefined; + get order() { + return this.#order; + } + + #orders!: R extends RawNumberId ? undefined : number[] | undefined; + get orders() { + return this.#orders; + } + + #higherOrder: [number, number] | undefined; + get higherOrder() { + return this.#higherOrder; + } + + /** + * Allows you to rename an existing + */ + rename(newRawId: I, map: IIdMap): boolean { + const parsed = this.parse(newRawId); + const renamer = () => { + this.applyParse(parsed.rawId, parsed.order, parsed.orders); + }; + return map._renameId(this, parsed.rawId, renamer); + } + + toRaw() { + return this.rawId; + } +} diff --git a/src/lib/classes/automaton/IdMap.ts b/src/lib/classes/automaton/IdMap.ts new file mode 100644 index 00000000..47a78b52 --- /dev/null +++ b/src/lib/classes/automaton/IdMap.ts @@ -0,0 +1,473 @@ +import type { RawId } from "./raw/RawId"; +import type { HasId } from "./HasId"; +import { Id } from "./Id"; +import { AutomatonClass } from "./AutomatonClass"; + +/** + * This is used to mark map entries that exist, but haven't gotten a member yet. + */ +const reserved = Symbol("reserved"); +type Reserved = typeof reserved; + +/** + * An internal enum used to hint which operation is being run. + * Depending on the operation, different constraints and operations will run to ensure consistency. + */ +enum SetMode { + /** + * This is ONLY used for internal functions where you know what you're doing. + */ + Internal, + /** + * This is used when adding a new class that did not previously exist. + */ + Add, + /** + * This is used when updating an existing class. + */ + Update, + /** + * This is used when deleting an existing class. + */ + Delete, +} + +/** + * Defines the part of an IdMap that deals with the ID's. + */ +export interface IIdMapKeys, IT, RT extends RawId> { + /** + * Will return a new ID based on the rawId you are requesting. + * If there already is an ID that uses this rawId, will return undefined. + */ + getNewIdFromRaw(rawId: RT): I | undefined; + + /** + * Will return a new ordered ID. The ID will follow a default naming scheme to ensure there is always a new name available. + */ + getNewOrderedId(): I; + + /** + * Will check if an ID exists in the store. + * This does not guarantee that there is a member associated with the id, it only guarantees that the ID exists. + */ + hasId(id: I | RT): boolean; + + /** + * Will get an ID if it exists. + */ + getId(rawId: RT): I | undefined; + + /** + * Allows you to rename an ID if the new raw ID is not used by another ID. + * If the rename was succesfull, will return `true`, otherwise will return `false`. + */ + renameId(id: I, newRawid: RT): boolean; + + /** + * DO NOT USE THIS. + * You should be using the `rename` method instead. + * + * This allows an ID to request a rename. + * The store will perfrm the removal of the ID, then request an ID rename, then re-add the ID. + */ + _renameId(id: I, newRawId: RT, renamer: () => void): boolean; + + keys(): Iterable; +} + +/** + * Defines the part of an IdMap that deals with members. + */ +export interface IIdMapValues< + C extends HasId, + I extends Id, + IT, + RT extends RawId, +> extends Iterable { + /** + * How many members are stored in the map. + */ + size: number; + + /** + * Will return true if that member, or a member with that ID exists in the map. + */ + has(input: C | I | RT): boolean; + + /** + * Will return the member with that ID, of it exists in the store. + */ + get(id: I): C | undefined; + + /** + * Will add a new member to the store. + * + * This fails if you try to add a member that already exists. + */ + add(member: C | I): IIdMapValues; + + /** + * Will update an existing member in the store. + * Note: you only need to use this if you are changing the entire member class to a new one. + * + * This fails if you try to add a member that doesn't exist. + */ + update(member: C | I): IIdMapValues; + + /** + * Will delete an existing member. + * Note: the map will still remember the ID, so if you want to use that ID again, you will have to keep it or get it with `getId`. + * + * This will fail if you try to delete a member that doesn't exist. + */ + delete(member: C | I): IIdMapValues; + + values(): Iterable; +} + +/** + * A map of members that all have unique ID's. + * Includes a lot of convenience functions to fix the crazy complexity of Ecdar ID's. + * Also includes a lot of checks to make sure you are using the ID's safely. + * + * This emulates a `Map`, but there are many differences. + */ +export interface IIdMap< + C extends HasId, + I extends Id, + IT, + RT extends RawId, +> extends IIdMapKeys, + IIdMapValues { + entries(): Iterable<[I, C]>; +} + +/** + * A map of members that all have unique ID's. + * Includes a lot of convenience functions to fix the crazy complexity of Ecdar ID's. + * Also includes a lot of checks to make sure you are using the ID's safely. + * + * This emulates a `Map`, but there are many differences. + */ +export abstract class IdMap< + C extends HasId, + I extends Id, + IT, + RT extends RawId, + R, + > + extends AutomatonClass + implements IIdMap +{ + constructor( + /** + * The constructor that should be used when generating a new ID. + */ + private idConstructor: new (id: number | IT | RT) => I, + ) { + super(); + } + + /** + * A map from rawId's to ID's. This is mainly used to check that all ID's will result in unique raw ID's. + */ + private rawIdMap = new Map(); + + /** + * The map of ID's that have order information. + */ + private orderedMap: (C | Reserved | undefined)[] = []; + + /** + * The map of ID's that have more than one order. + */ + private higherOrderedMap: ((C | Reserved | undefined)[] | undefined)[] = []; + + /** + * The map of ID's that have no order information. + */ + private unorderedMap = new Map(); + + /** + * The next value that can be used to generate a unique ID. + */ + private nextOrderedIndex: number = 0; + + /** + * Will find the next value that is free to generate a unique ID. + * + * This function will only look forwards. If an existing ID is deleted, + */ + private findNextOrderedIndex() { + while (this.orderedMap[this.nextOrderedIndex] !== undefined) { + this.nextOrderedIndex++; + } + } + + #size = 0; + get size() { + return this.#size; + } + + getNewIdFromRaw(rawId: RT) { + if (this.hasId(rawId)) return undefined; + + const newId = new this.idConstructor(rawId); + this.rememberId(newId); + + return newId; + } + + getNewOrderedId() { + this.findNextOrderedIndex(); + const newId = new this.idConstructor(this.nextOrderedIndex); + this.rememberId(newId); + + return newId; + } + + /** + * Adds an ID to the store of ID's. This store is used when checking that new or unknown ID's are unique. + */ + private rememberId(id: I) { + this.rawIdMap.set(id.rawId, id); + if (!this.has(id)) this.set(id, reserved, SetMode.Internal); + } + + hasId(id: I | RT) { + const rawId = id instanceof Id ? id.rawId : id; + if (this.rawIdMap.has(rawId)) { + if (id instanceof Id) this.idCheck(id); + return true; + } else { + return false; + } + } + + getId(rawId: RT) { + return this.rawIdMap.get(rawId); + } + + /** + * Forgets an ID. This should only be used when renaming ID's. + * + * WARNING: This is dangerous, as forgetting an existing ID might mean that the store will issue an identical ID later on. + * This will allow two members to have the same unique ID when they are serialized. + */ + private forgetId(id: I | RT) { + const rawId = id instanceof Id ? id.rawId : id; + return this.rawIdMap.delete(rawId); + } + + renameId(id: I, newRawId: RT): boolean { + return id.rename(newRawId, this); + } + + _renameId(id: I, newRawId: RT, renamer: () => void): boolean { + if (!this.hasId(id)) + throw new TypeError( + `Cannot rename an ID that is not part of this store (${id.rawId})`, + ); + if (this.hasId(newRawId)) return false; + const member = this.get(id); + this.delete(id); + this.forgetId(id); + renamer(); + if (id.rawId !== newRawId) + throw new Error( + `The renamer did not work. The rawId should now be ${newRawId}, but is instead ${id.rawId}`, + ); + this.rememberId(id); + this.set(id, member, SetMode.Internal); + return true; + } + + /** + * Will check that the ID is used safely. + * The rawId and ID must exclusivelty match each other, and the ID order must exclusively match the ID. + */ + private idCheck(id: I) { + const storedId = this.getId(id.rawId); + if (storedId) { + if (storedId !== id) { + throw new TypeError( + `This store uses a different Id that serializes to ${id.rawId}. You cannot use both in the same store.`, + ); + } + } else { + const storedMember = this.get(id); + if (this.isMember(storedMember) && storedMember.id !== id) { + throw new TypeError( + `This store uses a different Id (${storedMember.id.rawId}) that is uniqely comparable to ${id.rawId}. You cannot use both in the same store.`, + ); + } + } + } + + has(input: C | I | RT): boolean { + let id: I; + if (input instanceof Id) { + id = input; + } else if (typeof input === "number" || typeof input === "string") { + const candidate = this.getId(input); + if (!candidate) return false; + id = candidate; + } else { + id = input.id; + } + return this.isMember(this.get(id)); + } + + get(id: I): C | undefined { + if (!this.hasId(id)) return undefined; + this.idCheck(id); + if (id.order) { + return stripReserved(this.orderedMap[id.order]); + } else if (id.orders && id.higherOrder) { + return stripReserved( + this.higherOrderedMap[id.higherOrder[0]]?.[id.higherOrder[1]], + ); + } else if (typeof id.rawId === "string") { + return stripReserved(this.unorderedMap.get(id)); + } else { + throw new TypeError( + "An `id` should always have order, higherOrder, or a string-based rawId", + ); + } + + function stripReserved( + member: C | Reserved | undefined, + ): C | undefined { + return member === reserved ? undefined : member; + } + } + + add(member: C) { + return this.set(member.id, member, SetMode.Add); + } + + update(member: C) { + return this.set(member.id, member, SetMode.Update); + } + + delete(member: C | I) { + const id = member instanceof Id ? member : member.id; + return this.set(id, reserved, SetMode.Delete); + } + + /** + * This method deals with the complexity of changing values in the store. + * If the ID has some sort of order information, it is added to efficient arrays that enable some cool use cases. + * If the ID is just a raw string, it is added to a fallback store that can save anything. + */ + private set(id: I, member: C | Reserved | undefined, mode: SetMode) { + const memberIsMember = this.isMember(member); + if (memberIsMember && member.id !== id) + throw new TypeError("The index `Id` must match the member `Id`"); + const previousValueWasMember = this.isMember(this.get(id)); + if (mode !== SetMode.Delete && member === undefined) { + throw new TypeError( + `Cannot delete the member when not in "Delete" mode`, + ); + } else if (mode === SetMode.Delete && member !== undefined) { + throw new TypeError(`Cannot set the member when in "Delete" mode`); + } + this.idCheck(id); + switch (mode) { + case SetMode.Add: { + if (previousValueWasMember) + throw new TypeError( + `The member "${id.rawId}" cannot be added because it already exists`, + ); + this.rememberId(id); + break; + } + case SetMode.Update: { + if (!previousValueWasMember) + throw new TypeError( + `The member "${id.rawId}" cannot be updated because it does not exist`, + ); + break; + } + case SetMode.Delete: { + if (!previousValueWasMember) + throw new TypeError( + `The member "${id.rawId}" cannot be deleted because it does not exist`, + ); + break; + } + } + if (id.order) { + this.orderedMap[id.order] = member; + if (member === undefined) { + this.nextOrderedIndex = id.order; + } else { + this.findNextOrderedIndex(); + } + } else if (id.higherOrder) { + this.higherOrderedMap[id.higherOrder[0]] ??= []; + this.higherOrderedMap[id.higherOrder[0]]![id.higherOrder[1]] = + member; + } else if (typeof id.rawId === "string") { + if (member === reserved || member === undefined) { + this.unorderedMap.delete(id); + } else { + this.unorderedMap.set(id, member); + } + } else { + throw new TypeError( + "An `id` should always have order, higherOrder, or a string-based rawId", + ); + } + if (memberIsMember && !previousValueWasMember) { + this.#size++; + } else if (!memberIsMember && previousValueWasMember) { + this.#size--; + } + return this; + } + + *[Symbol.iterator]() { + const orderedValues = this.orderedMap.values(); + let orderedEntry = orderedValues.next(); + while (orderedEntry.done === false) { + if (this.isMember(orderedEntry.value)) yield orderedEntry.value; + orderedEntry = orderedValues.next(); + } + + const unorderedValues = this.unorderedMap.values(); + let unorderedEntry = unorderedValues.next(); + while (unorderedEntry.done === false) { + if (this.isMember(unorderedEntry.value)) yield unorderedEntry.value; + unorderedEntry = unorderedValues.next(); + } + } + + values = this[Symbol.iterator]; + + *keys() { + const values = this.values(); + let entry = values.next(); + while (!entry.done) { + yield entry.value.id; + entry = values.next(); + } + } + + *entries(): Iterable<[I, C]> { + const values = this.values(); + let entry = values.next(); + while (entry.done === false) { + yield [entry.value.id, entry.value]; + entry = values.next(); + } + } + + /** + * Checks whether a map value is a real member. + */ + private isMember(member: C | Reserved | undefined): member is C { + return member !== undefined && member !== reserved; + } +} diff --git a/src/lib/classes/automaton/IdScopedMap.ts b/src/lib/classes/automaton/IdScopedMap.ts new file mode 100644 index 00000000..0f3e8d80 --- /dev/null +++ b/src/lib/classes/automaton/IdScopedMap.ts @@ -0,0 +1,163 @@ +import type { RawId } from "./raw/RawId"; +import type { HasId } from "./HasId"; +import { Id } from "./Id"; +import { AutomatonClass } from "./AutomatonClass"; +import type { IIdMap, IdMap } from "./IdMap"; + +export abstract class IdScopedMap< + C extends HasId, + SC extends C, + I extends Id, + IT, + RT extends RawId, + R, + > + extends AutomatonClass + implements IIdMap +{ + constructor( + readonly map: IdMap, + private memberConstructor: new (...args: any) => SC, + ) { + super(); + } + + #size = 0; + get size() { + return this.#size; + } + + get getNewIdFromRaw() { + return this.map.getNewIdFromRaw; + } + + get getNewOrderedId() { + return this.map.getNewOrderedId; + } + + get hasId() { + return this.map.hasId; + } + + get getId() { + return this.map.getId; + } + + get renameId() { + return this.map.renameId; + } + + get _renameId() { + return this.map._renameId; + } + + has(input: SC | I | RT) { + if (!this.map.has(input)) return false; + + let id: I; + if (input instanceof Id) { + id = input; + } else if (typeof input === "string" || typeof input === "number") { + const gotId = this.map.getId(input); + if (gotId) { + id = gotId; + } else { + return false; + } + } else { + id = input.id; + } + + const member = this.map.get(id); + if (member === undefined) { + return true; + } else { + return this.isScopedMember(member); + } + } + + get(id: I): SC | undefined { + if (!this.hasId(id)) return undefined; + const member = this.map.get(id); + if (this.isScopedMember(member)) { + return member; + } else { + return undefined; + } + } + + add(member: SC) { + this.map.add(member); + this.#size++; + return this; + } + + update(member: SC) { + const existingMember = this.map.get(member.id); + if ( + existingMember !== undefined && + !this.isScopedMember(existingMember) + ) { + throw new TypeError( + "Cannot update a member in the main store that is not of the same class as this scoped store.", + ); + } + + this.map.update(member); + return this; + } + + delete(member: SC | I) { + const id = member instanceof Id ? member : member.id; + + const existingMember = this.map.get(id); + if ( + existingMember !== undefined && + !this.isScopedMember(existingMember) + ) { + throw new TypeError( + "Cannot delete a member in the main store that is not of the same class as this scoped store.", + ); + } + + this.map.delete(member); + this.#size--; + return this; + } + + *[Symbol.iterator]() { + const values = this.map.values(); + let entry = values.next(); + while (entry.done === false) { + if (this.isScopedMember(entry.value)) yield entry.value; + entry = values.next(); + } + } + + values = this[Symbol.iterator]; + + *keys() { + const values = this.values(); + let entry = values.next(); + while (!entry.done) { + yield entry.value.id; + entry = values.next(); + } + } + + *entries(): Iterable<[I, SC]> { + const values = this.values(); + let entry = values.next(); + while (entry.done === false) { + yield [entry.value.id, entry.value]; + entry = values.next(); + } + } + + /** + * Ensures that the value is the scoped member and nothing else. + */ + private isScopedMember(member: SC | C | undefined): member is SC { + return member !== undefined && member instanceof this.memberConstructor; + } +} diff --git a/src/lib/classes/automaton/IdSubsetMap.ts b/src/lib/classes/automaton/IdSubsetMap.ts new file mode 100644 index 00000000..80c76a13 --- /dev/null +++ b/src/lib/classes/automaton/IdSubsetMap.ts @@ -0,0 +1,143 @@ +import type { RawId } from "./raw/RawId"; +import type { HasId } from "./HasId"; +import { Id } from "./Id"; +import { AutomatonClass } from "./AutomatonClass"; +import type { IIdMap, IdMap } from "./IdMap"; + +export abstract class IdSubsetMap< + C extends HasId, + I extends Id, + IT, + RT extends RawId, + R, + > + extends AutomatonClass + implements IIdMap +{ + constructor(readonly map: IdMap) { + super(); + } + + private activeMembers: Set = new Set(); + + #size = 0; + get size() { + return this.#size; + } + + getNewIdFromRaw(rawId: RT) { + const newId = this.map.getNewIdFromRaw(rawId); + if (newId) this.activeMembers.add(newId); + return newId; + } + + getNewOrderedId() { + const newId = this.map.getNewOrderedId(); + if (newId) this.activeMembers.add(newId); + return newId; + } + + get hasId() { + return this.map.hasId; + } + + get getId() { + return this.map.getId; + } + + get renameId() { + return this.map.renameId; + } + + get _renameId() { + return this.map._renameId; + } + + has(input: C | I | RT) { + if (!this.map.has(input)) return false; + + let id: I; + if (input instanceof Id) { + id = input; + } else if (typeof input === "string" || typeof input === "number") { + const gotId = this.map.getId(input); + if (gotId) { + id = gotId; + } else { + return false; + } + } else { + id = input.id; + } + if (!this.activeMembers.has(id)) return false; + + return true; + } + + get(id: I): C | undefined { + if (!this.hasId(id)) return undefined; + if (!this.activeMembers.has(id)) return undefined; + return this.map.get(id); + } + + add(member: C) { + this.activeMembers.add(member.id); + this.map.add(member); + this.#size++; + return this; + } + + update(member: C) { + if (!this.activeMembers.has(member.id)) { + throw new TypeError( + "Cannot update a member in the main store that is not active in this store.", + ); + } + + this.map.update(member); + return this; + } + + delete(member: C | I) { + const id = member instanceof Id ? member : member.id; + if (!this.activeMembers.has(id)) { + throw new TypeError( + "Cannot delete a member in the main store that is not active in this store.", + ); + } + + this.map.delete(member); + this.activeMembers.delete(id); + this.#size--; + return this; + } + + *[Symbol.iterator]() { + const values = this.map.values(); + let entry = values.next(); + while (entry.done === false) { + if (this.activeMembers.has(entry.value.id)) yield entry.value; + entry = values.next(); + } + } + + values = this[Symbol.iterator]; + + *keys() { + const values = this.values(); + let entry = values.next(); + while (!entry.done) { + yield entry.value.id; + entry = values.next(); + } + } + + *entries(): Iterable<[I, C]> { + const values = this.values(); + let entry = values.next(); + while (entry.done === false) { + yield [entry.value.id, entry.value]; + entry = values.next(); + } + } +} diff --git a/src/lib/classes/automaton/Invariant.ts b/src/lib/classes/automaton/Invariant.ts deleted file mode 100644 index 62471805..00000000 --- a/src/lib/classes/automaton/Invariant.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Point } from "$lib/classes/draw"; - -export class Invariant { - /** - * The invariant function - * ex c >= 8 - * */ - fn: string; - - /** - * The position of the invariant - * */ - position: Point; - - constructor(fn: string, position: Point) { - this.fn = fn; - this.position = position; - } -} diff --git a/src/lib/classes/automaton/Location.ts b/src/lib/classes/automaton/Location.ts deleted file mode 100644 index 8a5e852d..00000000 --- a/src/lib/classes/automaton/Location.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Point } from "$lib/classes/draw"; -import { Nickname, Invariant, LocationType, Urgency, Raw } from "../automaton"; - -export class Location implements Raw.SerializeRaw, Raw.ToRaw { - /** - * The id of the Location - * */ - id: string; - - /** - * The position of the Location - * */ - position: Point; - - /** - * The Nickname of the Location - * */ - nickname: Nickname; - - /** - * The Invariant of the Location - * */ - invariant: Invariant; - - /** - * The Type of the Location - * */ - type: LocationType; - - /** - * The Urgency of the Location - * */ - urgency: Urgency; - - /** - * The Color of the Location - * */ - color: string; - - constructor( - id: string = "", - position: Point = new Point(0, 0), - nickname: Nickname = new Nickname("", new Point(0, 0)), - invariant: Invariant = new Invariant("", new Point(0, 0)), - type: LocationType = LocationType.NORMAL, - urgency: Urgency = Urgency.NORMAL, - color: string = "", - ) { - this.id = id; - this.position = position; - this.nickname = nickname; - this.invariant = invariant; - this.type = type; - this.urgency = urgency; - this.color = color; - } - - /** - * Converts the Location into a RawLocation - * */ - toRaw() { - return { - id: this.id, - nickname: this.nickname.name, - invariant: this.invariant.fn, - type: this.type, - urgency: this.urgency, - x: this.position.x, - y: this.position.y, - color: this.color, - nicknameX: this.nickname.position.x, - nicknameY: this.nickname.position.y, - invariantX: this.invariant.position.x, - invariantY: this.invariant.position.y, - }; - } - - serializeRaw() { - return JSON.stringify(this.toRaw()); - } - - /** - * Crates a Location from a RawLocation - * */ - static readonly fromRaw: Raw.FromRaw = (raw) => { - return new Location( - raw.id, - new Point(raw.x, raw.y), - new Nickname(raw.nickname, new Point(raw.nicknameX, raw.nicknameY)), - new Invariant( - raw.invariant, - new Point(raw.invariantX, raw.invariantY), - ), - raw.type, - raw.urgency as Urgency, - raw.color, - ); - }; - - /** - * Crates a Location from a JSON string matching a RawLocation - * */ - static readonly deserializeRaw: Raw.DeserializeRaw = (input) => { - const raw = Raw.parse(Raw.ZodRawLocation, input); - return Location.fromRaw(raw); - }; -} diff --git a/src/lib/classes/automaton/Locations.ts b/src/lib/classes/automaton/Locations.ts new file mode 100644 index 00000000..77776adb --- /dev/null +++ b/src/lib/classes/automaton/Locations.ts @@ -0,0 +1,174 @@ +import { IdMap, type IIdMapValues } from "./IdMap"; +import { Location } from "./component/Location"; +import { LocationId, type LocationIdInput } from "./component/LocationId"; +import type { RawStringId } from "./raw/RawId"; +import type { RawLocations } from "./component/raw/RawLocations"; +import { AutomatonClass, type FromRaw } from "./AutomatonClass"; +import { LocationType } from "./component/LocationType"; + +class PartialLocations extends IdMap< + Location, + LocationId, + LocationIdInput, + RawStringId, + RawLocations +> { + constructor() { + super(LocationId); + } + + /** + * Converts the PartialLocations to a RawLocations + * WARNING: This is not all locations, only all locations of a specific type. + */ + toRaw() { + return [...this].map((location) => location.toRaw()); + } +} + +type PartialLocationsMap = { + [key in keyof typeof LocationType]: PartialLocations; +}; + +export class Locations + extends AutomatonClass + implements IIdMapValues +{ + private partialLocationsMap: PartialLocationsMap = { + [LocationType.NORMAL]: new PartialLocations(), + [LocationType.UNIVERSAL]: new PartialLocations(), + [LocationType.INCONSISTENT]: new PartialLocations(), + }; + + getNewIdFromRaw(rawId: RawStringId) { + // We only use this ID to get the type, it should NOT be added to the store + const tempId = new LocationId(rawId); + + const partialLocations = this.partialLocationsMap[tempId.type]; + return partialLocations.getNewIdFromRaw(rawId); + } + + getNewOrderedId(type: LocationType) { + const partialLocations = this.partialLocationsMap[type]; + return partialLocations.getNewOrderedId(); + } + + hasId(id: LocationId | RawStringId): boolean { + let type: LocationType; + if (id instanceof LocationId) { + type = id.type; + } else { + // We only use this ID to get the type, it should NOT be added to the store + const tempId = new LocationId(id); + type = tempId.type; + } + + const partialLocations = this.partialLocationsMap[type]; + return partialLocations.hasId(id); + } + + getId(rawId: RawStringId): LocationId | undefined { + // We only use this ID to get the type, it should NOT be added to the store + const tempId = new LocationId(rawId); + + const partialLocations = this.partialLocationsMap[tempId.type]; + return partialLocations.getId(rawId); + } + + get size() { + return [...Object.values(this.partialLocationsMap)].reduce( + (total, next) => total + next.size, + 0, + ); + } + + has(input: Location | LocationId | RawStringId) { + if (input instanceof Location) { + const partialLocations = this.partialLocationsMap[input.type]; + return partialLocations.has(input); + } else { + for (const partialLocations of Object.values( + this.partialLocationsMap, + )) { + if (partialLocations.has(input)) return true; + } + return false; + } + } + + get(id: LocationId) { + const partialLocations = this.partialLocationsMap[id.type]; + return partialLocations.get(id); + } + + add(member: Location) { + const partialLocations = this.partialLocationsMap[member.id.type]; + return partialLocations.add(member); + } + + update(member: Location) { + const partialLocations = this.partialLocationsMap[member.id.type]; + return partialLocations.update(member); + } + + delete(member: Location) { + const partialLocations = this.partialLocationsMap[member.id.type]; + return partialLocations.delete(member); + } + + *[Symbol.iterator]() { + for (const partialLocations of Object.values( + this.partialLocationsMap, + )) { + yield* partialLocations; + } + } + + values = this[Symbol.iterator]; + + *keys() { + for (const partialLocations of Object.values( + this.partialLocationsMap, + )) { + yield* partialLocations.keys(); + } + } + + *entries() { + for (const partialLocations of Object.values( + this.partialLocationsMap, + )) { + yield* partialLocations.entries(); + } + } + + /** + * Converts the Locations to a RawLocations + */ + toRaw() { + return [...Object.values(this.partialLocationsMap)] + .map((partialLocations) => partialLocations.toRaw()) + .flat(); + } + + /** + * Converts a RawLocations to a Locations + */ + static readonly fromRaw: FromRaw = ( + raw, + ) => { + const locations = new Locations(); + for (const rawLocation of raw) { + const id = locations.getNewIdFromRaw(rawLocation.id); + + if (!id) + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Cannot load raw Locations where multiple id's are equivalent: ${rawLocation.id}`, + ); + + locations.add(Location.fromRaw(rawLocation, { id })); + } + return locations; + }; +} diff --git a/src/lib/classes/automaton/Nail.ts b/src/lib/classes/automaton/Nail.ts deleted file mode 100644 index 24aa8e9c..00000000 --- a/src/lib/classes/automaton/Nail.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Point } from "../draw"; -import type { Property } from "./Property"; - -/** - * Describes a nail on an edge - * */ -export class Nail { - /** - * The position of the nail - * */ - position: Point; - - /** - * The property of this nail - * */ - property: Property; - - constructor(property: Property, position: Point) { - this.position = position; - this.property = property; - } -} diff --git a/src/lib/classes/automaton/Named.ts b/src/lib/classes/automaton/Named.ts deleted file mode 100644 index fa7886c3..00000000 --- a/src/lib/classes/automaton/Named.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * The implementing type must contain a name value - * */ -export interface Named { - /** - * The name of the type - * */ - name: string; -} diff --git a/src/lib/classes/automaton/Nickname.ts b/src/lib/classes/automaton/Nickname.ts deleted file mode 100644 index e9a64d4f..00000000 --- a/src/lib/classes/automaton/Nickname.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { Point } from "$lib/classes/draw"; - -/** - * A user defined name for the location - * */ -export class Nickname { - /** - * The actual nickname - * */ - name: string; - - /** - * The position of the Nickname - * */ - position: Point; - - constructor(name: string, position: Point) { - this.name = name; - this.position = position; - } -} diff --git a/src/lib/classes/automaton/Operator.ts b/src/lib/classes/automaton/Operator.ts deleted file mode 100644 index e14ff076..00000000 --- a/src/lib/classes/automaton/Operator.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Point } from "$lib/classes/draw"; - -import { OperatorType } from "./OperatorType"; - -/** - * Defines an operator for a System - * */ -export class Operator { - /** - * The id of the operator - * must be unique and matches the id of a ComponentInstance - * */ - id: number = 0; - - /** - * The type of operator - * */ - type: OperatorType = OperatorType.COMPOSITION; - - /** - * The position of the operator - * */ - position: Point = new Point(0, 0); - - constructor( - id: number = 0, - type: OperatorType = OperatorType.COMPOSITION, - position: Point = new Point(0, 0), - ) { - this.id = id; - this.type = type; - this.position = position; - } -} diff --git a/src/lib/classes/automaton/Position.ts b/src/lib/classes/automaton/Position.ts new file mode 100644 index 00000000..74b2042f --- /dev/null +++ b/src/lib/classes/automaton/Position.ts @@ -0,0 +1,40 @@ +import { AutomatonClass, type FromRaw } from "./AutomatonClass"; +import type { RawPosition } from "./raw/RawPosition"; + +/** + * Describes a position in a coordinate system + */ +export class Position extends AutomatonClass { + constructor( + /** + * The X coordinate of the position + */ + public x: number, + + /** + * The Y coordinate of the position + */ + public y: number, + ) { + super(); + } + + /** + * Converts the Position to a RawPosition + */ + toRaw() { + return { + x: this.x, + y: this.y, + }; + } + + /** + * Converts a RawPosition to a Position + */ + static readonly fromRaw: FromRaw = ( + raw, + ) => { + return new Position(raw.x, raw.y); + }; +} diff --git a/src/lib/classes/automaton/Project.ts b/src/lib/classes/automaton/Project.ts new file mode 100644 index 00000000..bec1418e --- /dev/null +++ b/src/lib/classes/automaton/Project.ts @@ -0,0 +1,83 @@ +import { AutomatonClass, type FromRaw } from "./AutomatonClass"; +import type { RawProject } from "./raw/RawProject"; +import type { HasId } from "./HasId"; +import type { ProjectId } from "./ProjectId"; +import { Components } from "./component/Components"; +import { Locations } from "./Locations"; +import { Systems } from "./system/Systems"; +import { Query } from "./Query"; +import { SystemDeclarations } from "./SystemDeclarations"; +import { GlobalDeclarations } from "./GlobalDeclarations"; + +export class Project + extends AutomatonClass + implements HasId +{ + constructor( + /** + * The name of the project, and the name of the save folder + */ + public id: ProjectId, + + /** + * All locations in the project + */ + readonly locations: Locations = new Locations(), + + /** + * All components in the project + */ + public components: Components, + + /** + * All systems in the project + */ + public systems: Systems, + + /** + * All queries in the project + */ + public queries: Query[] = [], + + /** + * The system declarations in the project + */ + public systemDeclarations = new SystemDeclarations(), + + /** + * The global declarations in the project + */ + public globalDeclarations = new GlobalDeclarations(), + ) { + super(); + } + + toRaw() { + return { + name: this.id.toRaw(), + components: this.components.toRaw(), + systems: this.systems.toRaw(), + queries: this.queries.map((query) => query.toRaw()), + systemDeclarations: this.systemDeclarations.toRaw(), + globalDeclarations: this.globalDeclarations.toRaw(), + }; + } + + static readonly fromRaw: FromRaw = ( + raw, + { id }, + ) => { + const locations = new Locations(); + return new Project( + id, + locations, + Components.fromRaw(raw.components ?? [], { locations }), + Systems.fromRaw(raw.systems ?? []), + raw.queries + ? raw.queries.map((rawQuery) => Query.fromRaw(rawQuery)) + : undefined, + SystemDeclarations.fromRaw(raw.systemDeclarations ?? {}), + GlobalDeclarations.fromRaw(raw.globalDeclarations ?? {}), + ); + }; +} diff --git a/src/lib/classes/automaton/ProjectId.ts b/src/lib/classes/automaton/ProjectId.ts new file mode 100644 index 00000000..4e4bbe85 --- /dev/null +++ b/src/lib/classes/automaton/ProjectId.ts @@ -0,0 +1,36 @@ +import type { FromRaw } from "./AutomatonClass"; +import { Id } from "./Id"; +import type { RawId, RawStringId } from "./raw/RawId"; + +export class ProjectId extends Id { + protected parse(input: string | number) { + let rawId; + let order; + let orders; + if (typeof input === "number") { + rawId = `Project ${input}`; + order = input; + } else { + rawId = input; + const ordersMatches = [ + ...input.matchAll(/((?[\+\-]?\d+))/giu), + ]; + const ordersParsed = ordersMatches + .filter((match) => match.groups?.number) + .map((match) => parseInt(match.groups!.number)) + .filter((number) => !isNaN(number)); + if (ordersParsed.length === 1) { + order = ordersParsed[0]; + } else if (ordersParsed.length > 1) { + orders = [...ordersParsed]; + } + } + return { rawId, order, orders }; + } + + static readonly fromRaw: FromRaw = ( + raw, + ) => { + return new ProjectId(raw); + }; +} diff --git a/src/lib/classes/automaton/Projects.ts b/src/lib/classes/automaton/Projects.ts new file mode 100644 index 00000000..dccd8f38 --- /dev/null +++ b/src/lib/classes/automaton/Projects.ts @@ -0,0 +1,49 @@ +import type { FromRaw } from "./AutomatonClass"; +import { IdMap } from "./IdMap"; +import type { RawStringId } from "./raw/RawId"; +import { Project } from "./Project"; +import { ProjectId } from "./ProjectId"; +import type { RawProjects } from "./raw/RawProjects"; + +export class Projects extends IdMap< + Project, + ProjectId, + string, + RawStringId, + RawProjects +> { + constructor() { + super(ProjectId); + } + + toRaw() { + return [...this].map((project) => project.toRaw()); + } + + static readonly fromRaw: FromRaw = ( + raw, + ) => { + const projects = new Projects(); + for (const rawProject of raw) { + const id = rawProject.name + ? projects.getNewIdFromRaw(rawProject.name) + : projects.getNewOrderedId(); + + if (!id) + if (rawProject.name) { + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Cannot load raw Projects where multiple names are equivalent: "${rawProject.name}"`, + ); + } else { + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Encountered an unknown error while assigning a unique ID to an unnamed Project`, + ); + } + + projects.add(Project.fromRaw(rawProject, { id })); + } + return projects; + }; +} diff --git a/src/lib/classes/automaton/Property.ts b/src/lib/classes/automaton/Property.ts deleted file mode 100644 index baa01c21..00000000 --- a/src/lib/classes/automaton/Property.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Point } from "$lib/classes/draw"; - -import { PropertyType } from "./PropertyType"; - -/** - * The properties of a nail - * */ -export class Property { - /** - * The type of property - * */ - type: PropertyType = PropertyType.NONE; - - /** - * The position of the property - * */ - position: Point = new Point(0, 0); - constructor( - type: PropertyType = PropertyType.NONE, - position = new Point(0, 0), - ) { - this.type = type; - this.position = position; - } -} diff --git a/src/lib/classes/automaton/Query.ts b/src/lib/classes/automaton/Query.ts index cf143fdd..c653d488 100644 --- a/src/lib/classes/automaton/Query.ts +++ b/src/lib/classes/automaton/Query.ts @@ -1,118 +1,45 @@ -import { Backend, Raw } from "../automaton"; +import { AutomatonClass, type FromRaw } from "./AutomatonClass"; +import type { RawQuery } from "./raw/RawQuery"; /** - * # An Ecdar Query + * An Ecdar Query * What should the backend do - * */ -export class Query implements Raw.SerializeRaw, Raw.ToRaw { - /** - * The query string - * */ - query: string; - - /** - * A user defined comment - * */ - comment: string; - - /** - * ??? - * */ - isPeriodic: boolean; - //ignoredInputs - //ignoredOutputs + */ +export class Query extends AutomatonClass { + constructor( + /** + * The query string + */ + public query: string = "", + + /** + * A user defined comment + */ + public comment: string = "", + + /** + * Defines wether or not the query will be run periodically + */ + public isPeriodic: boolean = false, + ) { + super(); + } /** - * What backend should the query target - * */ - backend: Backend; - + * Converts the Query to a RawQuery + */ toRaw() { return { query: this.query, comment: this.comment, isPeriodic: this.isPeriodic, - ignoredInputs: {}, - ignoredOutputs: {}, - backend: this.backend, }; } - constructor( - query: string = "", - comment: string = "", - isPeriodic: boolean = false, - backend: Backend = Backend.REVEAAL, - ) { - this.query = query; - this.comment = comment; - this.isPeriodic = isPeriodic; - this.backend = backend; - } - - serializeRaw(): string { - const raw = this.toRaw(); - return JSON.stringify(raw); - } - - /** - * Creates a query from a RawQuery - * */ - static readonly fromRaw: Raw.FromRaw = (raw) => { - return new Query( - raw.query, - raw.comment, - raw.isPeriodic, - raw.backend as Backend, - ); - }; - - /** - * Creates a query from a JSON string matching a RawQuery - * */ - static readonly deserializeRaw: Raw.DeserializeRaw = (input) => { - const raw = Raw.parse(Raw.ZodRawQuery, input); - return Query.fromRaw(raw); - }; -} - -export class Queries implements Raw.SerializeRaw, Raw.ToRaw { - /** - * The array of queries - * */ - arr: Query[]; - - constructor(arr: Query[] = []) { - this.arr = arr; - } - - toRaw() { - return this.arr.map((query) => { - return query.toRaw(); - }); - } - - serializeRaw(): string { - const raw = this.toRaw(); - return JSON.stringify(raw); - } - - /** - * Creates all Queries from an JSON array matching an array of RawQuery - * */ - static readonly deserializeRaw: Raw.DeserializeRaw = (input) => { - const raw = Raw.parse(Raw.ZodRawQuery.array(), input); - return Queries.fromRaw(raw); - }; - /** - * Creates all Queries from an array of RawQuery - * */ - static readonly fromRaw: Raw.FromRaw = (raw) => { - return new Queries( - raw.map((rawSingle) => { - return Query.fromRaw(rawSingle); - }), - ); + * Converts a RawQuery to a Query + */ + static readonly fromRaw: FromRaw = (raw) => { + return new Query(raw.query, raw.comment, raw.isPeriodic); }; } diff --git a/src/lib/classes/automaton/RelativePosition.ts b/src/lib/classes/automaton/RelativePosition.ts new file mode 100644 index 00000000..9b27069b --- /dev/null +++ b/src/lib/classes/automaton/RelativePosition.ts @@ -0,0 +1,78 @@ +import { AutomatonClass, type FromRaw } from "./AutomatonClass"; +import type { Position } from "./Position"; +import type { RawRelativePosition } from "./raw/RawRelativePosition"; + +/** + * Describes a position in a coordinate system that is relative to another Position. + * + * If the reference Position changes coordinates, the RelativePosition will automatically change coordinates to keep the same relative distance as before. + */ +export class RelativePosition extends AutomatonClass { + constructor( + /** + * The Position that this RelativePosition will be placed relative to. + */ + public reference: Position, + + /** + * The relative x coordinate of the point. + * + * This number does not change automatically, the RelativePosition will always be this distance from the reference Position. + */ + public relativeX: number, + + /** + * The relative y coordinate of the point. + * + * This number does not change automatically, the RelativePosition will always be this distance from the reference Position. + */ + public relativeY: number, + ) { + super(); + } + + /** + * The absolute x coordinate of the RelativePosition. + * + * This value might change automatically to make sure the relative distance to the reference Position is correct. + */ + get x() { + return this.reference.x + this.relativeX; + } + set x(value) { + this.relativeX = value - this.reference.x; + } + + /** + * The absolute y coordinate of the RelativePosition. + * + * This value might change automatically to make sure the relative distance to the reference Position is correct. + */ + get y() { + return this.reference.y + this.relativeY; + } + set y(value) { + this.relativeY = value - this.reference.y; + } + + /** + * Converts the RelativePosition to a RawRelativePosition + */ + toRaw() { + return { + x: this.relativeX, + y: this.relativeY, + }; + } + + /** + * Converts a RawRelativePosition to a RelativePosition + */ + static readonly fromRaw: FromRaw< + RawRelativePosition, + { reference: Position }, + RelativePosition + > = (raw, references) => { + return new RelativePosition(references.reference, raw.x, raw.y); + }; +} diff --git a/src/lib/classes/automaton/System.ts b/src/lib/classes/automaton/System.ts deleted file mode 100644 index fc8990b0..00000000 --- a/src/lib/classes/automaton/System.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { Point, Dimensions } from "$lib/classes/draw"; -import { - Operator, - ComponentInstance, - SystemEdge, - Raw, - type Named, -} from "../automaton"; - -/** - * An Ecdar System - * */ -export class System - implements Raw.SerializeRaw, Raw.ToRaw, Named -{ - /** - * The name of the system - * */ - name: string; - - /** - * The description of the system - * */ - description: string; - - /** - * The position of the system - * */ - position: Point; - - /** - * The dimensions of the system - * */ - dimensions: Dimensions; - - /** - * The color of the system - * */ - color: string; - - /** - * The coordinate of root of the system - * */ - systemRootX: number; - - /** - * A list of components in the system - * */ - componentInstances: ComponentInstance[]; - - /** - * A list of operators in the system - * */ - operators: Operator[]; - - /** - * A list of edges in the system - * */ - edges: SystemEdge[]; - - constructor( - name: string = "", - description: string = "", - position: Point = new Point(0, 0), - dimensions: Dimensions = new Dimensions(0, 0), - color: string = "", - systemRootX: number = 0, - componentInstances: ComponentInstance[] = [], - operators: Operator[] = [], - edges: SystemEdge[] = [], - ) { - this.name = name; - this.description = description; - this.position = position; - this.dimensions = dimensions; - this.color = color; - this.systemRootX = systemRootX; - this.componentInstances = componentInstances; - this.operators = operators; - this.edges = edges; - } - - /** - * Converts the System into a RawSystem - * */ - toRaw() { - return { - name: this.name, - description: this.description, - x: this.position.x, - y: this.position.y, - width: this.dimensions.width, - height: this.dimensions.height, - color: this.color, - systemRootX: this.systemRootX, - componentInstances: this.componentInstances.map((instance) => { - return { - id: instance.id, - componentName: instance.name, - x: instance.position.x, - y: instance.position.y, - }; - }), - operators: this.operators.map((o) => { - return { - x: o.position.x, - y: o.position.y, - type: o.type, - id: o.id, - }; - }), - edges: this.edges.map((e) => { - return { - child: e.child, - parent: e.parent, - }; - }), - }; - } - - serializeRaw() { - return JSON.stringify(this.toRaw()); - } - - /** - * Creates a System from an object of type RawSystem - * */ - static readonly fromRaw: Raw.FromRaw = (raw) => { - return new System( - raw.name, - raw.description, - new Point(raw.x, raw.y), - new Dimensions(raw.width, raw.height), - raw.color, - raw.systemRootX, - raw.componentInstances.map((instance) => { - return new ComponentInstance( - instance.id, - instance.componentName, - new Point(instance.x, instance.y), - ); - }), - raw.operators.map((o) => { - return new Operator(o.id, o.type, new Point(o.x, o.y)); - }), - raw.edges.map((e) => { - return new SystemEdge(e.parent, e.child); - }), - ); - }; - - /** - * Creates a System from a JSON string of a RawSystem - * */ - static readonly deserializeRaw: Raw.DeserializeRaw = (input) => { - const raw = Raw.parse(Raw.ZodRawSystem, input); - return System.fromRaw(raw); - }; -} diff --git a/src/lib/classes/automaton/SystemDeclarations.ts b/src/lib/classes/automaton/SystemDeclarations.ts new file mode 100644 index 00000000..6b525a25 --- /dev/null +++ b/src/lib/classes/automaton/SystemDeclarations.ts @@ -0,0 +1,31 @@ +import type { FromRaw } from "./AutomatonClass"; +import { Declarations } from "./Declarations"; +import type { RawSystemDeclarations } from "./raw/RawSystemDeclarations"; + +/** + * The top level system declarations + */ +export class SystemDeclarations extends Declarations { + private id: "System declarations" = "System declarations"; + + /** + * Converts the SystemDeclarations to a RawSystemDeclarations + */ + toRaw() { + return { + name: this.id, + declarations: this.declarations, + }; + } + + /** + * Converts a RawSystemDeclarations to a SystemDeclarations + */ + static readonly fromRaw: FromRaw< + RawSystemDeclarations, + undefined, + SystemDeclarations + > = (raw) => { + return new SystemDeclarations(raw.declarations); + }; +} diff --git a/src/lib/classes/automaton/SystemEdge.ts b/src/lib/classes/automaton/SystemEdge.ts deleted file mode 100644 index 593eeeca..00000000 --- a/src/lib/classes/automaton/SystemEdge.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Describes the edges of a system. - * */ -export class SystemEdge { - /** - * The beginning of the edge - * */ - parent: number; - - /** - * The end of the edge - * */ - child: number; - - constructor(parent: number, child: number) { - this.parent = parent; - this.child = child; - } -} diff --git a/src/lib/classes/automaton/component/Component.ts b/src/lib/classes/automaton/component/Component.ts new file mode 100644 index 00000000..fef989b3 --- /dev/null +++ b/src/lib/classes/automaton/component/Component.ts @@ -0,0 +1,175 @@ +import { AutomatonClass, type FromRaw } from "../AutomatonClass"; +import type { RawComponent } from "./raw/RawComponent"; +import type { HasId } from "../HasId"; +import type { ComponentId } from "./ComponentId"; +import { LocationsSubset } from "./LocationsSubset"; +import type { Locations } from "../Locations"; +import type { LocationId } from "./LocationId"; +import { LocationEdges } from "./LocationEdges"; +import { Position } from "../Position"; +import { Dimensions } from "../Dimensions"; +import { LocationType } from "./LocationType"; +import { Location } from "./Location"; + +const defaultX = 0; +const defaultY = 0; + +const defaultWidth = 100; +const defaultHeight = 100; + +/** + * An Ecdar component + * It stores the edges and locations of a single automaton + */ +export class Component + extends AutomatonClass + implements HasId +{ + constructor( + /** + * The name of the component + */ + public id: ComponentId, + + /** + * The declarations of the component ex "clock t;" + */ + public declarations: string = "", + + /** + * A list of Locations in the System + */ + readonly locations: LocationsSubset, + + /** + * The component's initial location + */ + initialLocation: LocationId, + + /** + * A list of Edges in the Component + */ + public edges: LocationEdges, + + /** + * A description of the Component + */ + public description: string = "", + + /** + * The position of the Component + */ + public position: Position = new Position(defaultX, defaultY), + + /** + * The dimensions of the Component + */ + public dimensions: Dimensions = new Dimensions( + defaultWidth, + defaultHeight, + ), + + /** + * The color of the Component + */ + public color: string = "0", + + /** + * Include in periodic checks + * ! Some more information might be needed ! + */ + public includeInPeriodicCheck: boolean = false, + ) { + super(); + this.initialLocation = initialLocation; + } + + /** + * The component's initial location + */ + get initialLocation() { + return this.#initialLocation; + } + set initialLocation(id: LocationId) { + if (!this.locations.has(id)) + throw new TypeError( + "Cannot set an initial location that is not part of the list of locations", + ); + return; + } + #initialLocation!: LocationId; + + /** + * Converts the Component to a RawComponent + */ + toRaw() { + return { + name: this.id.toRaw(), + declarations: this.declarations, + locations: this.locations.toRaw(), + edges: this.edges.toRaw(), + description: this.description, + x: this.position.x, + y: this.position.y, + width: this.dimensions.width, + height: this.dimensions.height, + color: this.color, + includeInPeriodicCheck: this.includeInPeriodicCheck, + }; + } + + /** + * Converts a RawComponent to a Component + */ + static readonly fromRaw: FromRaw< + RawComponent, + { id: ComponentId; locations: Locations }, + Component + > = (raw, { id, locations }) => { + const locationsSubset = LocationsSubset.fromRaw(raw.locations, { + locationsMap: locations, + }); + return new Component( + id, + raw.declarations, + locationsSubset, + findInitialLocation(locations, raw.locations), + LocationEdges.fromRaw(raw.edges, { locations: locationsSubset }), + raw.description, + Position.fromRaw({ x: raw.x ?? defaultX, y: raw.y ?? defaultY }), + Dimensions.fromRaw({ + width: raw.width ?? defaultWidth, + height: raw.height ?? defaultHeight, + }), + raw.color, + raw.includeInPeriodicCheck, + ); + }; +} + +function findInitialLocation( + locations: Locations, + locationsRaw: RawComponent["locations"], +): LocationId { + const initialLocationsRaw = []; + for (const location of locationsRaw) { + if (location.type === "INITIAL") initialLocationsRaw.push(location.id); + } + if (initialLocationsRaw.length === 0) { + const newId = locations.getNewOrderedId(LocationType.NORMAL); + locations.add(new Location(newId)); + initialLocationsRaw.push(newId.toRaw()); + } else if (initialLocationsRaw.length < 1) { + throw new TypeError( + "Cannot load a Component that has more than one initial Locations", + ); + } + + const initialLocation = locations.getId(initialLocationsRaw[0]); + if (!initialLocation) + throw new TypeError( + "This should never happen, the initial location should have been loaded into the Locations store", + ); + + return initialLocation; +} diff --git a/src/lib/classes/automaton/component/ComponentId.ts b/src/lib/classes/automaton/component/ComponentId.ts new file mode 100644 index 00000000..ad9df5ce --- /dev/null +++ b/src/lib/classes/automaton/component/ComponentId.ts @@ -0,0 +1,8 @@ +import { Id } from "../Id"; +import type { RawId, RawStringId } from "../raw/RawId"; + +export class ComponentId extends Id { + parse(rawId: string) { + return { rawId, order: undefined, orders: undefined }; + } +} diff --git a/src/lib/classes/automaton/component/Components.ts b/src/lib/classes/automaton/component/Components.ts new file mode 100644 index 00000000..a6660003 --- /dev/null +++ b/src/lib/classes/automaton/component/Components.ts @@ -0,0 +1,43 @@ +import type { FromRaw } from "../AutomatonClass"; +import { IdMap } from "../IdMap"; +import type { Locations } from "../Locations"; +import type { RawStringId } from "../raw/RawId"; +import { Component } from "./Component"; +import { ComponentId } from "./ComponentId"; +import type { RawComponents } from "./raw/RawComponents"; + +export class Components extends IdMap< + Component, + ComponentId, + string, + RawStringId, + RawComponents +> { + constructor() { + super(ComponentId); + } + + toRaw() { + return [...this].map((component) => component.toRaw()); + } + + static readonly fromRaw: FromRaw< + RawComponents, + { locations: Locations }, + Components + > = (raw, { locations }) => { + const components = new Components(); + for (const rawComponent of raw) { + const id = components.getNewIdFromRaw(rawComponent.name); + + if (!id) + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Cannot load raw Components where multiple names are equivalent: ${rawComponent.name}`, + ); + + components.add(Component.fromRaw(rawComponent, { id, locations })); + } + return components; + }; +} diff --git a/src/lib/classes/automaton/component/Invariant.ts b/src/lib/classes/automaton/component/Invariant.ts new file mode 100644 index 00000000..d75a6f60 --- /dev/null +++ b/src/lib/classes/automaton/component/Invariant.ts @@ -0,0 +1,51 @@ +import { AutomatonClass, type FromRaw } from "../AutomatonClass"; +import type { RawInvariant } from "./raw/RawInvariant"; +import { RelativePosition } from "../RelativePosition"; +import type { Position } from "../Position"; + +const defaultRelativeX = 0; +const defaultRelativeY = 100; + +export class Invariant extends AutomatonClass { + constructor( + /** + * The invariant function + * ex c >= 8 + */ + public fn: string, + + /** + * The position of the invariant + */ + public position: RelativePosition, + ) { + super(); + } + + toRaw() { + const rawPosition = this.position.toRaw(); + + return { + invariant: this.fn, + invariantX: rawPosition.x, + invariantY: rawPosition.y, + }; + } + + static readonly fromRaw: FromRaw< + RawInvariant, + { positionReference: Position }, + Invariant + > = (raw, references) => { + return new Invariant( + raw.invariant, + RelativePosition.fromRaw( + { + x: raw.invariantX ?? defaultRelativeX, + y: raw.invariantY ?? defaultRelativeY, + }, + { reference: references.positionReference }, + ), + ); + }; +} diff --git a/src/lib/classes/automaton/component/Location.ts b/src/lib/classes/automaton/component/Location.ts new file mode 100644 index 00000000..f27a238b --- /dev/null +++ b/src/lib/classes/automaton/component/Location.ts @@ -0,0 +1,177 @@ +import { AutomatonClass, type FromRaw } from "../AutomatonClass"; +import type { RawLocation } from "./raw/RawLocation"; +import type { LocationId } from "./LocationId"; +import { LocationType } from "./LocationType"; +import { LocationUrgency } from "./LocationUrgency"; +import { Position } from "../Position"; +import { Nickname } from "./Nickname"; +import { Invariant } from "./Invariant"; + +const defaultX = 0; +const defaultY = 0; + +export class Location extends AutomatonClass { + constructor( + /** + * The id of the Location + */ + id: LocationId, + + /** + * The Type of the Location + */ + type: LocationType = LocationType.NORMAL, + + /** + * The Urgency of the Location + */ + public urgency: LocationUrgency = LocationUrgency.NORMAL, + + /** + * The Color of the Location + */ + public color: string = "", + + /** + * The position of the Location + */ + position: Position = new Position(defaultX, defaultY), + + /** + * The Nickname of the Location + */ + nickname?: Nickname, + + /** + * The Invariant of the Location + */ + invariant?: Invariant, + ) { + super(); + this.id = id; + this.type = type; + this.position = position; + this.nickname = nickname; + this.invariant = invariant; + } + + #id!: LocationId; + get id() { + return this.#id; + } + set id(value) { + this.#id = value; + this.typeCheck(); + } + + #type!: LocationType; + get type() { + return this.#type; + } + set type(value) { + this.#type = value; + this.typeCheck(); + } + + #position!: Position; + get position() { + return this.#position; + } + set position(value) { + this.#position = value; + this.positionCheck(); + } + + #nickname: Nickname | undefined; + get nickname() { + return this.#nickname; + } + set nickname(value) { + this.#nickname = value; + this.positionCheck(); + } + + #invariant: Invariant | undefined; + get invariant() { + return this.#invariant; + } + set invariant(value) { + this.#invariant = value; + this.positionCheck(); + } + + private typeCheck() { + /** + * TODO: Ideally this warning should be an error, but it is likely that real projects will fail this check. + */ + if (this.type !== this.id.type) { + console.warn( + `Having a location (${this.id.rawId}) of type ${this.type} with an ID of type ${this.id.type} seems like a bad idea.`, + ); + } + } + private positionCheck() { + if ( + this.nickname && + this.nickname.position.reference !== this.position + ) { + throw new TypeError( + `The Nickname should be placed relatively to the Position of this Location (${this.id.rawId})`, + ); + } + if ( + this.invariant && + this.invariant.position.reference !== this.position + ) { + throw new TypeError( + `The Invariant should be placed relatively to the Position of this Location ${this.id.rawId}`, + ); + } + } + + /** + * Converts the Location to a RawLocation + */ + toRaw() { + return { + id: this.id.toRaw(), + type: this.type, + urgency: this.urgency, + color: this.color, + ...this.position.toRaw(), + ...this.nickname?.toRaw(), + ...this.invariant?.toRaw(), + }; + } + + /** + * Converts a RawLocation to a Location + */ + static readonly fromRaw: FromRaw< + RawLocation, + { id: LocationId }, + Location + > = (raw, { id }) => { + const position = Position.fromRaw({ + x: raw.x ?? defaultX, + y: raw.y ?? defaultY, + }); + return new Location( + id, + raw.type === "INITIAL" ? id.type : raw.type, + raw.urgency, + raw.color, + position, + typeof raw.nickname === "string" + ? Nickname.fromRaw(raw as { nickname: string }, { + positionReference: position, + }) + : undefined, + typeof raw.invariant === "string" + ? Invariant.fromRaw(raw as { invariant: string }, { + positionReference: position, + }) + : undefined, + ); + }; +} diff --git a/src/lib/classes/automaton/component/LocationEdge.ts b/src/lib/classes/automaton/component/LocationEdge.ts new file mode 100644 index 00000000..c8f7cc12 --- /dev/null +++ b/src/lib/classes/automaton/component/LocationEdge.ts @@ -0,0 +1,119 @@ +import { AutomatonClass, type FromRaw } from "../AutomatonClass"; +import type { RawLocationEdge } from "./raw/RawLocationEdge"; +import type { LocationEdgeId } from "./LocationEdgeId"; +import type { LocationId } from "./LocationId"; +import { LocationEdgeStatus } from "./LocationEdgeStatus"; +import { LocationEdgeNail } from "./LocationEdgeNail"; + +/** + * An Ecdar Edge + * Used to define edges between Locations in an Ecdar Component + */ +export class LocationEdge extends AutomatonClass { + constructor( + /** + * The id of the edge + */ + public id: LocationEdgeId, + + /** + * Unused + */ + public group: string = "", + + /** + * The id of the source location + */ + public source: LocationId, + + /** + * The id of the target location + */ + public target: LocationId, + + /** + * The status of the edge + * - Input or + * - Output + */ + public status: LocationEdgeStatus = LocationEdgeStatus.INPUT, + + /** + * Unused + */ + public select: string = "", + + /** + * The guard of the edge + * ex "c <= 7" + */ + public guard: string = "", + + /** + * The update of the edge + * ex "c := 7" + */ + public update: string = "", + + /** + * The input OR output variable of the edge + */ + public sync: string = "", + + /** + * Unused + */ + public isLocked: boolean = true, + + /** + * The nails of the edge + * Modifies the path that the edge takes + * Defines properties on the edge + */ + public nails: LocationEdgeNail[] = [], + ) { + super(); + } + + /** + * Converts a LocationEdge to a RawLoctionEdge + */ + toRaw() { + return { + id: this.id.toRaw(), + group: this.group, + sourceLocation: this.source.toRaw(), + targetLocation: this.target.toRaw(), + status: this.status, + select: this.select, + guard: this.guard, + update: this.update, + sync: this.sync, + isLocked: this.isLocked, + nails: this.nails.map((nail) => nail.toRaw()), + }; + } + + /** + * Converts a RawLocationEdge to a LocationEdge + */ + static readonly fromRaw: FromRaw< + RawLocationEdge, + { id: LocationEdgeId; source: LocationId; target: LocationId }, + LocationEdge + > = (raw, { id, source, target }) => { + return new LocationEdge( + id, + raw.group, + source, + target, + raw.status, + raw.select, + raw.guard, + raw.update, + raw.sync, + raw.isLocked, + raw.nails?.map((rawNail) => LocationEdgeNail.fromRaw(rawNail)), + ); + }; +} diff --git a/src/lib/classes/automaton/component/LocationEdgeId.ts b/src/lib/classes/automaton/component/LocationEdgeId.ts new file mode 100644 index 00000000..9e2e68f8 --- /dev/null +++ b/src/lib/classes/automaton/component/LocationEdgeId.ts @@ -0,0 +1,33 @@ +import { Id } from "../Id"; +import type { RawStringId } from "../raw/RawId"; +import type { FromRaw } from "../AutomatonClass"; + +type LocationEdgeIdInput = RawStringId | number; +export class LocationEdgeId extends Id { + protected parse(id: LocationEdgeIdInput) { + let rawId; + let order; + let orders; + if (typeof id === "number") { + rawId = `E${id}`; + order = id; + } else { + rawId = id; + const ordersMatches = [ + ...id.matchAll(/(?E)?(?[\+\-]?\d+)/giu), + ].map((match) => parseInt(match.groups?.number || "0")); + if (ordersMatches.length === 1) { + order = ordersMatches[0]; + } else if (ordersMatches.length > 1) { + orders = ordersMatches; + } + } + return { rawId, order, orders }; + } + + static readonly fromRaw: FromRaw = ( + raw, + ) => { + return new LocationEdgeId(raw); + }; +} diff --git a/src/lib/classes/automaton/component/LocationEdgeNail.ts b/src/lib/classes/automaton/component/LocationEdgeNail.ts new file mode 100644 index 00000000..1c80b9f0 --- /dev/null +++ b/src/lib/classes/automaton/component/LocationEdgeNail.ts @@ -0,0 +1,50 @@ +import { AutomatonClass, type FromRaw } from "../AutomatonClass"; +import type { RawNail } from "./raw/RawNail"; +import { Position } from "../Position"; +import { NailProperty } from "./NailProperty"; + +/** + * Describes a nail on an edge + */ +export class LocationEdgeNail extends AutomatonClass { + constructor( + /** + * The position of the nail + */ + readonly position: Position, + + /** + * The property of this nail + */ + readonly property: NailProperty, + ) { + super(); + if (property.position.reference !== position) + throw new TypeError( + "The Property should be placed relatively to the Position of this Nail", + ); + } + + /** + * Converts the Nail to a RawNail + */ + toRaw() { + return { + ...this.position.toRaw(), + ...this.property.toRaw(), + }; + } + + /** + * Converts a RawNail to a Nail + */ + static readonly fromRaw: FromRaw = ( + raw, + ) => { + const position = Position.fromRaw(raw); + return new LocationEdgeNail( + position, + NailProperty.fromRaw(raw, { positionReference: position }), + ); + }; +} diff --git a/src/lib/classes/automaton/component/LocationEdgeStatus.ts b/src/lib/classes/automaton/component/LocationEdgeStatus.ts new file mode 100644 index 00000000..36324cbb --- /dev/null +++ b/src/lib/classes/automaton/component/LocationEdgeStatus.ts @@ -0,0 +1,9 @@ +/** + * The status of an Edge + * - Input or + * - Output + */ +export enum LocationEdgeStatus { + INPUT = "INPUT", + OUTPUT = "OUTPUT", +} diff --git a/src/lib/classes/automaton/component/LocationEdges.ts b/src/lib/classes/automaton/component/LocationEdges.ts new file mode 100644 index 00000000..6b1622bd --- /dev/null +++ b/src/lib/classes/automaton/component/LocationEdges.ts @@ -0,0 +1,65 @@ +import { IdMap } from "../IdMap"; +import { LocationEdgeId } from "./LocationEdgeId"; +import type { RawStringId } from "../raw/RawId"; +import type { RawLocationEdges } from "./raw/RawLocationEdges"; +import type { FromRaw } from "../AutomatonClass"; +import type { LocationsSubset } from "./LocationsSubset"; +import { LocationEdge } from "./LocationEdge"; + +export class LocationEdges extends IdMap< + LocationEdge, + LocationEdgeId, + RawStringId, + RawStringId, + RawLocationEdges +> { + constructor() { + super(LocationEdgeId); + } + + /** + * Converts the LocationEdges to a RawLocationEdges + */ + toRaw() { + return [...this].map((edge) => edge.toRaw()); + } + + /** + * Converts a RawLocationEdges to a LocationEdges + */ + static readonly fromRaw: FromRaw< + RawLocationEdges, + { locations: LocationsSubset }, + LocationEdges + > = (raw, { locations }) => { + const locationEdges = new LocationEdges(); + for (const rawLocationEdge of raw) { + const id = locationEdges.getNewIdFromRaw(rawLocationEdge.id); + + if (!id) + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Cannot load raw LocationEdges where multiple id's are equivalent: ${rawLocationEdge.id}`, + ); + + const source = locations.getId(rawLocationEdge.sourceLocation); + if (!source || !locations.has(rawLocationEdge.sourceLocation)) + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Cannot load raw LocationEdge ${rawLocationEdge.id} because its source Location doesn't exist: ${rawLocationEdge.sourceLocation}`, + ); + + const target = locations.getId(rawLocationEdge.targetLocation); + if (!target || !locations.has(rawLocationEdge.targetLocation)) + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Cannot load raw LocationEdge ${rawLocationEdge.id} because its target Location doesn't exist: ${rawLocationEdge.targetLocation}`, + ); + + locationEdges.add( + LocationEdge.fromRaw(rawLocationEdge, { id, source, target }), + ); + } + return locationEdges; + }; +} diff --git a/src/lib/classes/automaton/component/LocationId.ts b/src/lib/classes/automaton/component/LocationId.ts new file mode 100644 index 00000000..60dd2007 --- /dev/null +++ b/src/lib/classes/automaton/component/LocationId.ts @@ -0,0 +1,87 @@ +import { Id } from "../Id"; +import type { RawId, RawStringId } from "../raw/RawId"; +import { LocationType } from "./LocationType"; +import type { FromRaw } from "../AutomatonClass"; + +export type LocationIdInput = RawId | { type: LocationType; order: number }; +export class LocationId extends Id { + #type: LocationType = LocationType.NORMAL; + get type() { + return this.#type; + } + + protected parse(input: LocationIdInput) { + let rawId; + let order; + let orders; + if (typeof input === "object") { + let precursor = "L"; + switch (input.type) { + case LocationType.UNIVERSAL: { + precursor = "UL"; + break; + } + case LocationType.INCONSISTENT: { + precursor = "IL"; + break; + } + } + rawId = `${precursor}${input.order}`; + order = input.order; + } else if (typeof input === "number") { + rawId = `L${input}`; + order = input; + } else { + rawId = input; + const ordersMatches = [ + ...input.matchAll( + /(?U)?(?I)?(?L)?(?[\+\-]?\d+)/giu, + ), + ]; + const ordersParsed: { + isUniversal: boolean; + isInconsistent: boolean; + isLocation: boolean; + number: number; + }[] = []; + for (const orderMatch of ordersMatches) { + if (orderMatch.groups) { + const parsed = { + isUniversal: Boolean(orderMatch.groups.isUniversal), + isInconsistent: Boolean( + orderMatch.groups.isInconsistent, + ), + isLocation: Boolean(orderMatch.groups.isLocation), + number: parseInt(orderMatch.groups.number || "0"), + }; + ordersParsed.push(parsed); + + /** + * TODO: This check is pretty naive. + * We should check that the Id is only universal, inconsistent, or normal. + * If it is more than one, we should throw an error. + * This would require testing to make sure it is compatible with some of the more crazy Id's that Ecdar generates. + */ + if (orderMatch.groups.isUniversal) { + this.#type = LocationType.UNIVERSAL; + } + if (orderMatch.groups.isInconsistent) { + this.#type = LocationType.INCONSISTENT; + } + } + } + if (ordersParsed.length === 1) { + order = ordersParsed[0].number; + } else if (ordersParsed.length > 1) { + orders = ordersParsed.map((parsed) => parsed.number); + } + } + return { rawId, order, orders }; + } + + static readonly fromRaw: FromRaw = ( + raw, + ) => { + return new LocationId(raw); + }; +} diff --git a/src/lib/classes/automaton/component/LocationType.ts b/src/lib/classes/automaton/component/LocationType.ts new file mode 100644 index 00000000..8f000f38 --- /dev/null +++ b/src/lib/classes/automaton/component/LocationType.ts @@ -0,0 +1,19 @@ +/** + * The type of a location + */ +export enum LocationType { + /** + * No modifiers + */ + NORMAL = "NORMAL", + + /** + * ????? + */ + UNIVERSAL = "UNIVERSAL", + + /** + * ????? + */ + INCONSISTENT = "INCONSISTENT", +} diff --git a/src/lib/classes/automaton/component/LocationUrgency.ts b/src/lib/classes/automaton/component/LocationUrgency.ts new file mode 100644 index 00000000..4b93fb6d --- /dev/null +++ b/src/lib/classes/automaton/component/LocationUrgency.ts @@ -0,0 +1,25 @@ +/** + * What the backend should do when visiting a Location + * Not implemented in Reveeal yet + */ +export enum LocationUrgency { + /** + * Normal TIOA rules apply + */ + NORMAL = "NORMAL", + + /** + * ???? + */ + PROHIBITED = "PROHIBITED", + + /** + * Make a move instantly no matter the timer + */ + URGENT = "URGENT", + + /** + * ???? + */ + COMMITTED = "COMMITTED", +} diff --git a/src/lib/classes/automaton/component/Locations.ts b/src/lib/classes/automaton/component/Locations.ts new file mode 100644 index 00000000..d7ba5bb9 --- /dev/null +++ b/src/lib/classes/automaton/component/Locations.ts @@ -0,0 +1,45 @@ +import { IdMap } from "../IdMap"; +import { Location } from "./Location"; +import { LocationId } from "./LocationId"; +import type { RawStringId } from "../raw/RawId"; +import type { RawLocations } from "./raw/RawLocations"; +import type { FromRaw } from "../AutomatonClass"; + +export class Locations extends IdMap< + Location, + LocationId, + RawStringId, + RawLocations +> { + constructor() { + super(LocationId); + } + + /** + * Converts the Locations to a RawLocations + */ + toRaw() { + return [...this].map((location) => location.toRaw()); + } + + /** + * Converts a RawLocations to a Locations + */ + static readonly fromRaw: FromRaw = ( + raw, + ) => { + const locations = new Locations(); + for (const rawLocation of raw) { + const id = locations.getNewIdFromRaw(rawLocation.id); + + if (!id) + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Cannot load raw Locations where multiple id's are equivalent: ${rawLocation.id}`, + ); + + locations.add(Location.fromRaw(rawLocation, { id })); + } + return locations; + }; +} diff --git a/src/lib/classes/automaton/component/LocationsSubset.ts b/src/lib/classes/automaton/component/LocationsSubset.ts new file mode 100644 index 00000000..f75a72cc --- /dev/null +++ b/src/lib/classes/automaton/component/LocationsSubset.ts @@ -0,0 +1,39 @@ +import { Location } from "./Location"; +import type { LocationId, LocationIdInput } from "./LocationId"; +import type { RawStringId } from "../raw/RawId"; +import type { RawLocations } from "./raw/RawLocations"; +import type { FromRaw } from "../AutomatonClass"; +import { IdSubsetMap } from "../IdSubsetMap"; +import type { Locations } from "../Locations"; + +export class LocationsSubset extends IdSubsetMap< + Location, + LocationId, + LocationIdInput, + RawStringId, + RawLocations +> { + toRaw() { + return [...this].map((location) => location.toRaw()); + } + + static readonly fromRaw: FromRaw< + RawLocations, + { locationsMap: Locations }, + LocationsSubset + > = (raw, { locationsMap }) => { + const locationsSubset = new LocationsSubset(locationsMap); + for (const rawLocation of raw) { + const id = locationsSubset.getNewIdFromRaw(rawLocation.id); + + if (!id) + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Cannot load raw Locations where multiple id's are equivalent: ${rawLocation.id}`, + ); + + locationsSubset.add(Location.fromRaw(rawLocation, { id })); + } + return locationsSubset; + }; +} diff --git a/src/lib/classes/automaton/component/Nail.ts b/src/lib/classes/automaton/component/Nail.ts new file mode 100644 index 00000000..1c4bd5ed --- /dev/null +++ b/src/lib/classes/automaton/component/Nail.ts @@ -0,0 +1,48 @@ +import { AutomatonClass, type FromRaw } from "../AutomatonClass"; +import type { RawNail } from "./raw/RawNail"; +import { Position } from "../Position"; +import { Property } from "./Property"; + +/** + * Describes a nail on an edge + */ +export class Nail extends AutomatonClass { + constructor( + /** + * The position of the nail + */ + readonly position: Position, + + /** + * The property of this nail + */ + readonly property: Property, + ) { + super(); + if (property.position.reference !== position) + throw new TypeError( + "The Property should be placed relatively to the Position of this Nail", + ); + } + + /** + * Converts the Nail to a RawNail + */ + toRaw() { + return { + ...this.position.toRaw(), + ...this.property.toRaw(), + }; + } + + /** + * Converts a RawNail to a Nail + */ + static readonly fromRaw: FromRaw = (raw) => { + const position = Position.fromRaw(raw); + return new Nail( + position, + Property.fromRaw(raw, { positionReference: position }), + ); + }; +} diff --git a/src/lib/classes/automaton/component/NailProperty.ts b/src/lib/classes/automaton/component/NailProperty.ts new file mode 100644 index 00000000..94e8e61d --- /dev/null +++ b/src/lib/classes/automaton/component/NailProperty.ts @@ -0,0 +1,48 @@ +import { AutomatonClass, type FromRaw } from "../AutomatonClass"; +import type { RawProperty } from "./raw/RawProperty"; +import { NailPropertyType } from "./NailPropertyType"; +import { RelativePosition } from "../RelativePosition"; +import type { Position } from "../Position"; + +/** + * The properties of a nail + */ +export class NailProperty extends AutomatonClass { + constructor( + /** + * The type of property + */ + public type: NailPropertyType = NailPropertyType.NONE, + + /** + * The position of the property + */ + readonly position: RelativePosition, + ) { + super(); + } + + toRaw() { + const rawPosition = this.position.toRaw(); + + return { + propertyType: this.type, + propertyX: rawPosition.x, + propertyY: rawPosition.y, + }; + } + + static readonly fromRaw: FromRaw< + RawProperty, + { positionReference: Position }, + NailProperty + > = (raw, references) => { + return new NailProperty( + raw.propertyType, + RelativePosition.fromRaw( + { x: raw.propertyX, y: raw.propertyY }, + { reference: references.positionReference }, + ), + ); + }; +} diff --git a/src/lib/classes/automaton/component/NailPropertyType.ts b/src/lib/classes/automaton/component/NailPropertyType.ts new file mode 100644 index 00000000..62fa7ac8 --- /dev/null +++ b/src/lib/classes/automaton/component/NailPropertyType.ts @@ -0,0 +1,30 @@ +/** + * The types of Properties that Ecdar suports, + * They match the fields of an Edge + */ +export enum NailPropertyType { + /** + * Matches no field + */ + NONE = "NONE", + + /** + * Links to the select property of an Edge + */ + SELECTION = "SELECTION", + + /** + * Links to the guard property of an Edge + */ + GUARD = "GUARD", + + /** + * Links to the sync property of an Edge + */ + SYNCHRONIZATION = "SYNCHRONIZATION", + + /** + * Links to the update property of an Edge + */ + UPDATE = "UPDATE", +} diff --git a/src/lib/classes/automaton/component/Nickname.ts b/src/lib/classes/automaton/component/Nickname.ts new file mode 100644 index 00000000..b883434f --- /dev/null +++ b/src/lib/classes/automaton/component/Nickname.ts @@ -0,0 +1,59 @@ +import { AutomatonClass, type FromRaw } from "../AutomatonClass"; +import type { RawNickname } from "./raw/RawNickname"; +import { RelativePosition } from "../RelativePosition"; +import type { Position } from "../Position"; + +const defaultRelativeX = 0; +const defaultRelativeY = 100; + +/** + * A user defined name for the location + */ +export class Nickname extends AutomatonClass { + constructor( + /** + * The actual nickname + */ + public name: string, + + /** + * The position of the Nickname + */ + public position: RelativePosition, + ) { + super(); + } + + /** + * Converts the Nickname to a RawNickname + */ + toRaw() { + const rawPosition = this.position.toRaw(); + + return { + nickname: this.name, + nicknameX: rawPosition.x, + nicknameY: rawPosition.y, + }; + } + + /** + * Converts a RawNickname to a Nickname + */ + static readonly fromRaw: FromRaw< + RawNickname, + { positionReference: Position }, + Nickname + > = (raw, references) => { + return new Nickname( + raw.nickname, + RelativePosition.fromRaw( + { + x: raw.nicknameX ?? defaultRelativeX, + y: raw.nicknameY ?? defaultRelativeY, + }, + { reference: references.positionReference }, + ), + ); + }; +} diff --git a/src/lib/classes/automaton/component/Property.ts b/src/lib/classes/automaton/component/Property.ts new file mode 100644 index 00000000..925633dc --- /dev/null +++ b/src/lib/classes/automaton/component/Property.ts @@ -0,0 +1,48 @@ +import { AutomatonClass, type FromRaw } from "../AutomatonClass"; +import type { RawProperty } from "./raw/RawProperty"; +import { PropertyType } from "./PropertyType"; +import { RelativePosition } from "../RelativePosition"; +import type { Position } from "../Position"; + +/** + * The properties of a nail + */ +export class Property extends AutomatonClass { + constructor( + /** + * The type of property + */ + public type: PropertyType = PropertyType.NONE, + + /** + * The position of the property + */ + readonly position: RelativePosition, + ) { + super(); + } + + toRaw() { + const rawPosition = this.position.toRaw(); + + return { + propertyType: this.type, + propertyX: rawPosition.x, + propertyY: rawPosition.y, + }; + } + + static readonly fromRaw: FromRaw< + RawProperty, + { positionReference: Position }, + Property + > = (raw, references) => { + return new Property( + raw.propertyType, + RelativePosition.fromRaw( + { x: raw.propertyX, y: raw.propertyY }, + { reference: references.positionReference }, + ), + ); + }; +} diff --git a/src/lib/classes/automaton/PropertyType.ts b/src/lib/classes/automaton/component/PropertyType.ts similarity index 91% rename from src/lib/classes/automaton/PropertyType.ts rename to src/lib/classes/automaton/component/PropertyType.ts index 5c8f2729..63751e96 100644 --- a/src/lib/classes/automaton/PropertyType.ts +++ b/src/lib/classes/automaton/component/PropertyType.ts @@ -1,30 +1,30 @@ /** * The types of Properties that Ecdar suports, * They match the fields of an Edge - * */ + */ export enum PropertyType { /** * Matches no field - * */ + */ NONE = "NONE", /** * Links to the select property of an Edge - * */ + */ SELECTION = "SELECTION", /** * Links to the guard property of an Edge - * */ + */ GUARD = "GUARD", /** * Links to the sync property of an Edge - * */ + */ SYNCHRONIZATION = "SYNCHRONIZATION", /** * Links to the update property of an Edge - * */ + */ UPDATE = "UPDATE", } diff --git a/src/lib/classes/automaton/Status.ts b/src/lib/classes/automaton/component/Status.ts similarity index 95% rename from src/lib/classes/automaton/Status.ts rename to src/lib/classes/automaton/component/Status.ts index 3151349b..b1fde321 100644 --- a/src/lib/classes/automaton/Status.ts +++ b/src/lib/classes/automaton/component/Status.ts @@ -2,7 +2,7 @@ * The status of an Edge * - Input or * - Output - * */ + */ export enum Status { INPUT = "INPUT", OUTPUT = "OUTPUT", diff --git a/src/lib/classes/automaton/Urgency.ts b/src/lib/classes/automaton/component/Urgency.ts similarity index 90% rename from src/lib/classes/automaton/Urgency.ts rename to src/lib/classes/automaton/component/Urgency.ts index e035e48b..74bd4038 100644 --- a/src/lib/classes/automaton/Urgency.ts +++ b/src/lib/classes/automaton/component/Urgency.ts @@ -1,25 +1,25 @@ /** * What the backend should do when visiting a Location * Not implemented in Reveeal yet - * */ + */ export enum Urgency { /** * Normal TIOA rules apply - * */ + */ NORMAL = "NORMAL", /** * ???? - * */ + */ PROHIBITED = "PROHIBITED", /** * Make a move instantly no matter the timer - * */ + */ URGENT = "URGENT", /** * ???? - * */ + */ COMMITTED = "COMMITTED", } diff --git a/src/lib/classes/automaton/component/raw/RawComponent.ts b/src/lib/classes/automaton/component/raw/RawComponent.ts new file mode 100644 index 00000000..e65c7493 --- /dev/null +++ b/src/lib/classes/automaton/component/raw/RawComponent.ts @@ -0,0 +1,25 @@ +import { ZodRawLocation } from "./RawLocation"; +import { ZodRawLocationEdge } from "./RawLocationEdge"; +import { ZodRawPosition } from "../../raw/RawPosition"; +import { ZodRawDimensions } from "../../raw/RawDimensions"; +import { z } from "zod"; + +/** + * Used to parse a RawComponent through Zod + */ +export const ZodRawComponent = z.object({ + name: z.string(), + declarations: z.string().optional(), + locations: ZodRawLocation.array().optional().default([]), + edges: ZodRawLocationEdge.array().optional().default([]), + description: z.string().optional(), + ...ZodRawPosition.partial().shape, + ...ZodRawDimensions.partial().shape, + color: z.string().optional(), + includeInPeriodicCheck: z.boolean().optional(), +}); + +/** + * The raw Object for a Component that is used to save and communicate in JSON. + */ +export type RawComponent = z.infer; diff --git a/src/lib/classes/automaton/raw/RawComponentInstance.ts b/src/lib/classes/automaton/component/raw/RawComponentInstance.ts similarity index 75% rename from src/lib/classes/automaton/raw/RawComponentInstance.ts rename to src/lib/classes/automaton/component/raw/RawComponentInstance.ts index cae3c36b..daf2d7c7 100644 --- a/src/lib/classes/automaton/raw/RawComponentInstance.ts +++ b/src/lib/classes/automaton/component/raw/RawComponentInstance.ts @@ -1,13 +1,13 @@ import { z } from "zod"; +import { ZodRawPosition } from "../../raw/RawPosition"; /** * Used to parse a RawComponentInstance through Zod - * */ + */ export const ZodRawComponentInstance = z.object({ id: z.number(), componentName: z.string(), - x: z.number(), - y: z.number(), + ...ZodRawPosition.shape, }); export type RawComponentInstance = z.infer; diff --git a/src/lib/classes/automaton/component/raw/RawComponents.ts b/src/lib/classes/automaton/component/raw/RawComponents.ts new file mode 100644 index 00000000..11926cd1 --- /dev/null +++ b/src/lib/classes/automaton/component/raw/RawComponents.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; +import { ZodRawComponent } from "./RawComponent"; + +/** + * Used to parse a RawComponents through Zod + */ +export const ZodRawComponents = z.array(ZodRawComponent); + +/** + * The raw Object for a Components that is used to save and communicate in JSON. + */ +export type RawComponents = z.infer; diff --git a/src/lib/classes/automaton/component/raw/RawInvariant.ts b/src/lib/classes/automaton/component/raw/RawInvariant.ts new file mode 100644 index 00000000..6dd2f3cf --- /dev/null +++ b/src/lib/classes/automaton/component/raw/RawInvariant.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; +import { ZodRawNumber } from "../../raw/RawNumber"; + +/** + * Used to parse a RawInvariant through Zod + */ +export const ZodRawInvariant = z.object({ + invariant: z.string(), + invariantX: ZodRawNumber.optional(), + invariantY: ZodRawNumber.optional(), +}); + +export type RawInvariant = z.infer; diff --git a/src/lib/classes/automaton/component/raw/RawLocation.ts b/src/lib/classes/automaton/component/raw/RawLocation.ts new file mode 100644 index 00000000..47df5831 --- /dev/null +++ b/src/lib/classes/automaton/component/raw/RawLocation.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; +import { LocationType } from "../LocationType"; +import { LocationUrgency } from "../LocationUrgency"; +import { ZodRawPosition } from "../../raw/RawPosition"; +import { ZodRawNickname } from "./RawNickname"; +import { ZodRawInvariant } from "./RawInvariant"; + +/** + * Used to parse a RawLoaction through Zod + */ +export const ZodRawLocation = z.object({ + id: z.string(), + type: z.enum(["INITIAL", ...Object.values(LocationType)]).optional(), + urgency: z.nativeEnum(LocationUrgency).optional(), + color: z.string().optional(), + ...ZodRawPosition.partial().shape, + ...ZodRawNickname.partial().shape, + ...ZodRawInvariant.partial().shape, +}); + +/** + * The raw Object for a Location that is used to save and communicate in JSON. + */ +export type RawLocation = z.infer; diff --git a/src/lib/classes/automaton/component/raw/RawLocationEdge.ts b/src/lib/classes/automaton/component/raw/RawLocationEdge.ts new file mode 100644 index 00000000..0f03b720 --- /dev/null +++ b/src/lib/classes/automaton/component/raw/RawLocationEdge.ts @@ -0,0 +1,25 @@ +import { z } from "zod"; +import { ZodRawNail } from "./RawNail"; +import { LocationEdgeStatus } from "../LocationEdgeStatus"; + +/** + * Used to parse a RawLocationEdge through Zod + */ +export const ZodRawLocationEdge = z.object({ + id: z.string(), + group: z.string().optional(), + sourceLocation: z.string(), + targetLocation: z.string(), + status: z.nativeEnum(LocationEdgeStatus).optional(), + select: z.string().optional(), + sync: z.string().optional(), + guard: z.string().optional(), + update: z.string().optional(), + isLocked: z.boolean().optional(), + nails: ZodRawNail.array().optional(), +}); + +/** + * The raw Object for a LocationEdge that is used to save and communicate in JSON. + */ +export type RawLocationEdge = z.infer; diff --git a/src/lib/classes/automaton/component/raw/RawLocationEdges.ts b/src/lib/classes/automaton/component/raw/RawLocationEdges.ts new file mode 100644 index 00000000..d03aea64 --- /dev/null +++ b/src/lib/classes/automaton/component/raw/RawLocationEdges.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; +import { ZodRawLocationEdge } from "./RawLocationEdge"; + +/** + * Used to parse a RawLocationEdges through Zod + */ +export const ZodRawLocationEdges = z.array(ZodRawLocationEdge); + +/** + * The raw Object for a LocationEdges that is used to save and communicate in JSON. + */ +export type RawLocationEdges = z.infer; diff --git a/src/lib/classes/automaton/LocationType.ts b/src/lib/classes/automaton/component/raw/RawLocationType.ts similarity index 87% rename from src/lib/classes/automaton/LocationType.ts rename to src/lib/classes/automaton/component/raw/RawLocationType.ts index 056cc3c3..de7e0b01 100644 --- a/src/lib/classes/automaton/LocationType.ts +++ b/src/lib/classes/automaton/component/raw/RawLocationType.ts @@ -5,32 +5,32 @@ * - UNIVARSAL * - INCONSISTENT or * - ANY - * */ + */ export enum LocationType { /** * It is an initial Location * only one allowed per Component - * */ + */ INITIAL = "INITIAL", /** - * No modifires - * */ + * No modifiers + */ NORMAL = "NORMAL", /** * ????? - * */ + */ UNIVERSAL = "UNIVERSAL", /** * ????? - * */ + */ INCONSISTENT = "INCONSISTENT", /** * DO NOT USE * Reveaal will panic - * */ + */ ANY = "ANY", } diff --git a/src/lib/classes/automaton/component/raw/RawLocations.ts b/src/lib/classes/automaton/component/raw/RawLocations.ts new file mode 100644 index 00000000..d818b0bc --- /dev/null +++ b/src/lib/classes/automaton/component/raw/RawLocations.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; +import { ZodRawLocation } from "./RawLocation"; + +/** + * Used to parse a RawLocations through Zod + */ +export const ZodRawLocations = z.array(ZodRawLocation); + +/** + * The raw Object for a Locations that is used to save and communicate in JSON. + */ +export type RawLocations = z.infer; diff --git a/src/lib/classes/automaton/component/raw/RawNail.ts b/src/lib/classes/automaton/component/raw/RawNail.ts new file mode 100644 index 00000000..169da7cc --- /dev/null +++ b/src/lib/classes/automaton/component/raw/RawNail.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; +import { ZodRawPosition } from "../../raw/RawPosition"; +import { ZodRawProperty } from "./RawProperty"; + +/** + * Used to parse a RawNail through Zod + */ +export const ZodRawNail = z.object({ + ...ZodRawPosition.shape, + ...ZodRawProperty.shape, +}); + +export type RawNail = z.infer; diff --git a/src/lib/classes/automaton/component/raw/RawNickname.ts b/src/lib/classes/automaton/component/raw/RawNickname.ts new file mode 100644 index 00000000..f67e6149 --- /dev/null +++ b/src/lib/classes/automaton/component/raw/RawNickname.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; +import { ZodRawPosition } from "../../raw/RawPosition"; + +/** + * Used to parse a RawNickname through Zod + */ +export const ZodRawNickname = z.object({ + nickname: z.string(), + nicknameX: ZodRawPosition.shape.x.optional(), + nicknameY: ZodRawPosition.shape.y.optional(), +}); + +export type RawNickname = z.infer; diff --git a/src/lib/classes/automaton/component/raw/RawProperty.ts b/src/lib/classes/automaton/component/raw/RawProperty.ts new file mode 100644 index 00000000..3819eaaa --- /dev/null +++ b/src/lib/classes/automaton/component/raw/RawProperty.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; +import { NailPropertyType } from "../NailPropertyType"; +import { ZodRawPosition } from "../../raw/RawPosition"; + +/** + * Used to parse a RawProperty through Zod + */ +export const ZodRawProperty = z.object({ + propertyType: z.nativeEnum(NailPropertyType), + propertyX: ZodRawPosition.shape.x, + propertyY: ZodRawPosition.shape.y, +}); + +export type RawProperty = z.infer; diff --git a/src/lib/classes/automaton/id/HasId.ts b/src/lib/classes/automaton/id/HasId.ts new file mode 100644 index 00000000..8efa6bd6 --- /dev/null +++ b/src/lib/classes/automaton/id/HasId.ts @@ -0,0 +1,5 @@ +import type { Id } from "./Id"; + +export interface HasId> { + id: I; +} diff --git a/src/lib/classes/automaton/id/Id.ts b/src/lib/classes/automaton/id/Id.ts new file mode 100644 index 00000000..c516f781 --- /dev/null +++ b/src/lib/classes/automaton/id/Id.ts @@ -0,0 +1,16 @@ +import type { RawIdFromString, RawIdFromNumber } from "./raw/RawId"; +import { AutomatonClass } from "../AutomatonClass"; + +export abstract class Id< + R extends RawIdFromString | RawIdFromNumber, +> extends AutomatonClass { + constructor(private id: number | string) { + super(); + } + + abstract get name(): string; + + abstract get order(): R extends RawIdFromNumber + ? number + : number | undefined; +} diff --git a/src/lib/classes/automaton/id/IdMap.ts b/src/lib/classes/automaton/id/IdMap.ts new file mode 100644 index 00000000..ea5e0e27 --- /dev/null +++ b/src/lib/classes/automaton/id/IdMap.ts @@ -0,0 +1,178 @@ +import { AutomatonClass } from "../AutomatonClass"; +import type { HasId } from "./HasId"; +import { Id } from "./Id"; + +export abstract class IdSet, I extends Id, R> + extends AutomatonClass +{ + constructor( + private idConstructor: new (rawId: number | string) => I, + ){super()} + + private idMap = new Map(); + + private orderedMap: (C | undefined)[] = []; + private unorderedMap = new Map(); + + private nextOrderedIndex: number = 0; + private findNextOrderedIndex(){ + while(this.orderedMap[this.nextOrderedIndex] !== undefined){ + this.nextOrderedIndex++; + } + } + + #size = 0; + get size() { + return this.#size; + } + + getNewId(rawId: T extends string ? string : number | string | undefined): I | undefined { + if(!rawId){ + this.findNextOrderedIndex(); + rawId = this.nextOrderedIndex; + }else if(this.hasId(rawId)) { + throw new TypeError() + } + + const newId = new this.idConstructor(rawId); + this.setId(newId); + + return newId; + } + + private setId(id: I){ + return this.idMap.set(id.toRaw(), id); + } + + getId = this.idMap.get; + + hasId = this.idMap.has; + + add(member: C) { + const idOrder = member.id.order; + if (idOrder !== undefined) { + if (this.orderedMap[idOrder] !== undefined) { + throw new TypeError(`The member "${member.id.toRaw()}" cannot be added because it already exists`); + } + } else { + const rawId = member.id.toRaw(); + if (this.unorderedMap.has(rawId) === true) { + throw new TypeError(`The member "${member.id.toRaw()}" cannot be added because it already exists`); + } + } + return this.set(member); + } + + update(member: C){ + const idOrder = member.id.order; + if (idOrder !== undefined) { + if (this.orderedMap[idOrder] === undefined) { + throw new TypeError(`The member "${member.id.toRaw()}" cannot be updated because it does not exist`); + } + } else { + const rawId = member.id.toRaw(); + if (this.unorderedMap.has(rawId) === false) { + throw new TypeError(`The member "${member.id.toRaw()}" cannot be updated because it does not exist`); + } + } + return this.set(member); + } + + set(member: C) { + this.setId(member.id); + const idOrder = member.id.order; + if (idOrder !== undefined) { + if (this.orderedMap[idOrder] === undefined) { + this.#size++; + } + this.orderedMap[idOrder] = member; + this.findNextOrderedIndex(); + } else { + const rawId = member.id.toRaw(); + if (this.unorderedMap.has(rawId) === false) { + this.#size++; + } + this.unorderedMap.set(rawId, member); + } + return this; + } + + clear() { + this.#size = 0; + this.orderedMap.length = 0; + this.unorderedMap.clear(); + } + + delete(member: C | I) { + const id = member instanceof Id ? member : member.id; + const idOrder = id.order; + if (idOrder !== undefined) { + if (this.orderedMap[idOrder] !== undefined) { + this.orderedMap[idOrder] === undefined; + this.#size--; + if(idOrder < this.nextOrderedIndex){ + this.nextOrderedIndex = idOrder; + } + return true; + } else { + return false; + } + } else { + const deleted = this.unorderedMap.delete(id.toRaw()); + if (deleted === true) { + this.#size--; + } + return deleted; + } + } + + get(id: I) { + const idOrder = id.order; + if (idOrder !== undefined) { + return this.orderedMap[idOrder]; + } else { + return this.unorderedMap.get(id.toRaw()); + } + } + + has(member: C | I) { + const id = member instanceof Id ? member : member.id; + const idOrder = id.order; + if (idOrder !== undefined) { + return Boolean(this.orderedMap[idOrder]); + } else { + return this.unorderedMap.has(id.toRaw()); + } + } + + *[Symbol.iterator]() { + const orderedValues = this.orderedMap.values(); + let orderedEntry = orderedValues.next(); + while (orderedEntry.done === false) { + if (orderedEntry.value !== undefined) yield orderedEntry.value; + orderedEntry = orderedValues.next(); + } + + yield* this.unorderedMap.values(); + } + + values = this[Symbol.iterator]; + + *keys() { + const values = this.values(); + let entry = values.next(); + while (entry.done === false) { + yield entry.value.id; + entry = values.next(); + } + } + + *entries() { + const values = this.values(); + let entry = values.next(); + while (entry.done === false) { + yield [entry.value.id, entry.value]; + entry = values.next(); + } + } +} diff --git a/src/lib/classes/automaton/id/LocationId.ts b/src/lib/classes/automaton/id/LocationId.ts new file mode 100644 index 00000000..63d21bed --- /dev/null +++ b/src/lib/classes/automaton/id/LocationId.ts @@ -0,0 +1,17 @@ +import { Id } from "./Id"; +import type { RawIdFromString } from "./raw/RawId"; +import type { FromRaw } from "../AutomatonClass"; + +export class LocationId extends Id { + constructor(id: number) { + super(id); + } + + toRaw() { + return `L${this.id}`; + } + + static readonly fromRaw: FromRaw = ( + raw, + ) => {}; +} diff --git a/src/lib/classes/automaton/id/raw/RawId.ts b/src/lib/classes/automaton/id/raw/RawId.ts new file mode 100644 index 00000000..09775dbc --- /dev/null +++ b/src/lib/classes/automaton/id/raw/RawId.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; +import { ZodRawNumber } from "../../raw/RawNumber"; + +const RawIdFromString = z.string(); +const RawIdFromNumber = ZodRawNumber; + +/** + * Used to parse a RawId through Zod + */ +export const ZodRawId = z.union([RawIdFromString, RawIdFromNumber]); + +export type RawIdFromString = z.infer; +export type RawIdFromNumber = z.infer; diff --git a/src/lib/classes/automaton/raw.ts b/src/lib/classes/automaton/raw.ts deleted file mode 100644 index 201e5834..00000000 --- a/src/lib/classes/automaton/raw.ts +++ /dev/null @@ -1,56 +0,0 @@ -// This lib contains all the RAW data types that represents the data layout -// of Ecdar - -import type { z } from "zod"; -export { ZodRawComponent, type RawComponent } from "./raw/RawComponent"; -export { - ZodRawComponentInstance, - type RawComponentInstance, -} from "./raw/RawComponentInstance"; -export { ZodRawDeclaration, type RawDeclaration } from "./raw/RawDeclaration"; -export { ZodRawEdge, type RawEdge } from "./raw/RawEdge"; -export { ZodRawLocation, type RawLocation } from "./raw/RawLocation"; -export { ZodRawNail, type RawNail } from "./raw/RawNail"; -export { ZodRawOperator, type RawOperator } from "./raw/RawOperator"; -export { ZodRawQuery, type RawQuery } from "./raw/RawQuery"; -export { ZodRawSystem, type RawSystem } from "./raw/RawSystem"; -export { ZodRawSystemEdge, type RawSystemEdge } from "./raw/RawSystemEdge"; - -export interface SerializeRaw { - /** - * Serializes the object into a JSON string matching its raw counter part - */ - serializeRaw: () => string; -} - -/** - * A function type that should return the object T given a raw JSON string input - * */ -export type DeserializeRaw = (input: string) => T; - -export interface ToRaw { - /** - * Maps the object to its raw counter part - * */ - toRaw: () => R; -} - -/** - * A function type that should return the object T given its raw counterpart R - * */ -export type FromRaw = (raw: R) => T; - -/** - * Parses the json string according to the ZOD schema - */ -export function parse( - schema: z.ZodType, - json: string, -): z.infer { - const parse = schema.safeParse(JSON.parse(json)); - if (parse.success) { - return parse.data; - } else { - throw parse.error; - } -} diff --git a/src/lib/classes/automaton/raw/RawComponent.ts b/src/lib/classes/automaton/raw/RawComponent.ts deleted file mode 100644 index 08cc4fb2..00000000 --- a/src/lib/classes/automaton/raw/RawComponent.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ZodRawLocation } from "./RawLocation"; -import { ZodRawEdge } from "./RawEdge"; -import { z } from "zod"; - -/** - * Used to parse a RawComponent through Zod - * */ -export const ZodRawComponent = z.object({ - name: z.string(), - declarations: z.string(), - locations: ZodRawLocation.array(), - edges: ZodRawEdge.array(), - description: z.string(), - x: z.number(), - y: z.number(), - width: z.number(), - height: z.number(), - color: z.string(), - includeInPeriodicCheck: z.boolean(), -}); - -/** - * The raw Object for a Component that is used to save and communicate in JSON. - * */ -export type RawComponent = z.infer; diff --git a/src/lib/classes/automaton/raw/RawDeclaration.ts b/src/lib/classes/automaton/raw/RawDeclarations.ts similarity index 62% rename from src/lib/classes/automaton/raw/RawDeclaration.ts rename to src/lib/classes/automaton/raw/RawDeclarations.ts index d38f5f8e..d181a0c3 100644 --- a/src/lib/classes/automaton/raw/RawDeclaration.ts +++ b/src/lib/classes/automaton/raw/RawDeclarations.ts @@ -3,13 +3,13 @@ import { DeclarationType } from "../DeclarationType"; /** * Used to parse a RawDecalaration through Zod - * */ -export const ZodRawDeclaration = z.object({ + */ +export const ZodRawDeclarations = z.object({ name: z.nativeEnum(DeclarationType), - declarations: z.string(), + declarations: z.string().optional(), }); /** * The raw Object for a Declaration that is used to save and communicate in JSON. - * */ -export type RawDeclaration = z.infer; + */ +export type RawDeclarations = z.infer; diff --git a/src/lib/classes/automaton/raw/RawDimensions.ts b/src/lib/classes/automaton/raw/RawDimensions.ts new file mode 100644 index 00000000..eeb381ff --- /dev/null +++ b/src/lib/classes/automaton/raw/RawDimensions.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; +import { ZodRawNumber } from "./RawNumber"; + +/** + * Used to parse a RawDimensions through Zod + */ +export const ZodRawDimensions = z.object({ + width: ZodRawNumber, + height: ZodRawNumber, +}); + +export type RawDimensions = z.infer; diff --git a/src/lib/classes/automaton/raw/RawEdge.ts b/src/lib/classes/automaton/raw/RawEdge.ts deleted file mode 100644 index f8b47eac..00000000 --- a/src/lib/classes/automaton/raw/RawEdge.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { z } from "zod"; -import { ZodRawNail } from "./RawNail"; -import { Status } from "../Status"; - -/** - * Used to parse a RawEdge through Zod - * */ -export const ZodRawEdge = z.object({ - id: z.string(), - group: z.string(), - sourceLocation: z.string(), - targetLocation: z.string(), - status: z.nativeEnum(Status), - select: z.string(), - sync: z.string(), - guard: z.string(), - update: z.string(), - isLocked: z.boolean(), - nails: ZodRawNail.array(), -}); - -/** - * The raw Object for an Edge that is used to save and communicate in JSON. - * */ -export type RawEdge = z.infer; diff --git a/src/lib/classes/automaton/raw/RawGlobalDeclarations.ts b/src/lib/classes/automaton/raw/RawGlobalDeclarations.ts new file mode 100644 index 00000000..75b7f330 --- /dev/null +++ b/src/lib/classes/automaton/raw/RawGlobalDeclarations.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; + +/** + * Used to parse a RawGlobalDeclaration through Zod + */ +export const ZodRawGlobalDeclarations = z.object({ + name: z.literal("Global declarations").optional(), + declarations: z.string().optional(), +}); + +/** + * The raw Object for a GlobalDeclarations that is used to save and communicate in JSON. + */ +export type RawGlobalDeclarations = z.infer; diff --git a/src/lib/classes/automaton/raw/RawId.ts b/src/lib/classes/automaton/raw/RawId.ts new file mode 100644 index 00000000..8e4127fb --- /dev/null +++ b/src/lib/classes/automaton/raw/RawId.ts @@ -0,0 +1,15 @@ +import { z } from "zod"; +import { ZodRawNumber } from "./RawNumber"; + +const ZodRawStringId = z.string(); +const ZodRawNumberId = ZodRawNumber; + +export type RawStringId = z.infer; +export type RawNumberId = z.infer; + +/** + * Used to parse a RawId through Zod + */ +export const ZodRawId = z.union([ZodRawStringId, ZodRawNumberId]); + +export type RawId = z.infer; diff --git a/src/lib/classes/automaton/raw/RawLocation.ts b/src/lib/classes/automaton/raw/RawLocation.ts deleted file mode 100644 index 4e8ebdd8..00000000 --- a/src/lib/classes/automaton/raw/RawLocation.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { z } from "zod"; -import { Urgency } from "../Urgency"; -import { LocationType } from "../LocationType"; - -/** - * Used to parse a RawLoaction through Zod - * */ -export const ZodRawLocation = z.object({ - id: z.string(), - nickname: z.string(), - invariant: z.string(), - type: z.nativeEnum(LocationType), - urgency: z.nativeEnum(Urgency), - x: z.number(), - y: z.number(), - color: z.string(), - nicknameX: z.number(), - nicknameY: z.number(), - invariantX: z.number(), - invariantY: z.number(), -}); - -/** - * The raw Object for a Location that is used to save and communicate in JSON. - * */ -export type RawLocation = z.infer; diff --git a/src/lib/classes/automaton/raw/RawNail.ts b/src/lib/classes/automaton/raw/RawNail.ts deleted file mode 100644 index 50379f05..00000000 --- a/src/lib/classes/automaton/raw/RawNail.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { z } from "zod"; -import { PropertyType } from "../PropertyType"; - -/** - * Used to parse a RawNail through Zod - * */ -export const ZodRawNail = z.object({ - x: z.number(), - y: z.number(), - propertyType: z.nativeEnum(PropertyType), - propertyX: z.number(), - propertyY: z.number(), -}); - -export type RawNail = z.infer; diff --git a/src/lib/classes/automaton/raw/RawNumber.ts b/src/lib/classes/automaton/raw/RawNumber.ts new file mode 100644 index 00000000..0421129b --- /dev/null +++ b/src/lib/classes/automaton/raw/RawNumber.ts @@ -0,0 +1,8 @@ +import { z } from "zod"; + +/** + * Used to parse a number through Zod + */ +export const ZodRawNumber = z.number().finite().safe(); + +export type RawNumber = z.infer; diff --git a/src/lib/classes/automaton/raw/RawPosition.ts b/src/lib/classes/automaton/raw/RawPosition.ts new file mode 100644 index 00000000..3dadcdb9 --- /dev/null +++ b/src/lib/classes/automaton/raw/RawPosition.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; +import { ZodRawNumber } from "./RawNumber"; + +/** + * Used to parse a RawPosition through Zod + */ +export const ZodRawPosition = z.object({ + x: ZodRawNumber.nonnegative(), + y: ZodRawNumber.nonnegative(), +}); + +export type RawPosition = z.infer; diff --git a/src/lib/classes/automaton/raw/RawProject.ts b/src/lib/classes/automaton/raw/RawProject.ts new file mode 100644 index 00000000..75e76064 --- /dev/null +++ b/src/lib/classes/automaton/raw/RawProject.ts @@ -0,0 +1,23 @@ +import { z } from "zod"; +import { ZodRawComponent } from "../component/raw/RawComponent"; +import { ZodRawSystem } from "../system/raw/RawSystem"; +import { ZodRawQuery } from "./RawQuery"; +import { ZodRawSystemDeclarations } from "./RawSystemDeclarations"; +import { ZodRawGlobalDeclarations } from "./RawGlobalDeclarations"; + +/** + * Used to parse a RawProject through Zod + */ +export const ZodRawProject = z.object({ + name: z.string().optional(), + components: ZodRawComponent.array().optional(), + systems: ZodRawSystem.array().optional(), + queries: ZodRawQuery.array().optional(), + systemDeclarations: ZodRawSystemDeclarations.optional(), + globalDeclarations: ZodRawGlobalDeclarations.optional(), +}); + +/** + * The raw Object for a Project that is used to save and communicate in JSON. + */ +export type RawProject = z.infer; diff --git a/src/lib/classes/automaton/raw/RawProjects.ts b/src/lib/classes/automaton/raw/RawProjects.ts new file mode 100644 index 00000000..d005dbe1 --- /dev/null +++ b/src/lib/classes/automaton/raw/RawProjects.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; +import { ZodRawProject } from "./RawProject"; + +/** + * Used to parse a RawProjects through Zod + */ +export const ZodRawProjects = z.array(ZodRawProject); + +/** + * The raw Object for a Projects that is used to save and communicate in JSON. + */ +export type RawProjects = z.infer; diff --git a/src/lib/classes/automaton/raw/RawQuery.ts b/src/lib/classes/automaton/raw/RawQuery.ts index f72df01a..2066ca72 100644 --- a/src/lib/classes/automaton/raw/RawQuery.ts +++ b/src/lib/classes/automaton/raw/RawQuery.ts @@ -1,19 +1,16 @@ import { z } from "zod"; -import { Backend } from "../Backend"; +//import { Backend } from "../Backend"; /** * Used to parse a RawQuery through Zod - * */ + */ export const ZodRawQuery = z.object({ - query: z.string(), - comment: z.string(), - isPeriodic: z.boolean(), - ignoredInputs: z.object({}), // Ignored for now - ignoredOutputs: z.object({}), // Ignored for now - backend: z.nativeEnum(Backend), + query: z.string().optional(), + comment: z.string().optional(), + isPeriodic: z.boolean().optional(), }); /** * The raw Object for a Query that is used to save and communicate in JSON. - * */ + */ export type RawQuery = z.infer; diff --git a/src/lib/classes/automaton/raw/RawRelativePosition.ts b/src/lib/classes/automaton/raw/RawRelativePosition.ts new file mode 100644 index 00000000..acbea719 --- /dev/null +++ b/src/lib/classes/automaton/raw/RawRelativePosition.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; +import { ZodRawNumber } from "./RawNumber"; + +/** + * Used to parse a RawRelativePosition through Zod + */ +export const ZodRawRelativePosition = z.object({ + x: ZodRawNumber, + y: ZodRawNumber, +}); + +export type RawRelativePosition = z.infer; diff --git a/src/lib/classes/automaton/raw/RawSystemDeclarations.ts b/src/lib/classes/automaton/raw/RawSystemDeclarations.ts new file mode 100644 index 00000000..c17d6282 --- /dev/null +++ b/src/lib/classes/automaton/raw/RawSystemDeclarations.ts @@ -0,0 +1,14 @@ +import { z } from "zod"; + +/** + * Used to parse a RawSystemDeclaration through Zod + */ +export const ZodRawSystemDeclarations = z.object({ + name: z.literal("System declarations").optional(), + declarations: z.string().optional(), +}); + +/** + * The raw Object for a SystemDeclarations that is used to save and communicate in JSON. + */ +export type RawSystemDeclarations = z.infer; diff --git a/src/lib/classes/automaton/raw/RawSystemEdge.ts b/src/lib/classes/automaton/raw/RawSystemEdge.ts deleted file mode 100644 index 9fd20f28..00000000 --- a/src/lib/classes/automaton/raw/RawSystemEdge.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { z } from "zod"; - -/** - * Used to parse a RawSystemEdge through Zod - * */ -export const ZodRawSystemEdge = z.object({ - child: z.number(), - parent: z.number(), -}); - -export type RawSystemEdge = z.infer; diff --git a/src/lib/classes/automaton/raw/utils.ts b/src/lib/classes/automaton/raw/utils.ts new file mode 100644 index 00000000..dc08a01d --- /dev/null +++ b/src/lib/classes/automaton/raw/utils.ts @@ -0,0 +1,66 @@ +/** + * @file + * This lib contains functions and interfaces that help convert raw components to main components, and vice-versa + */ +import type { z } from "zod"; + +/** + * An interface for a class that has an equivalent Raw class. It should be trivial to change between the raw and non-raw class. + * + * NOTE: This interface should also include the static member "FromRaw", but because typescript support for static members is really bad, + * we just define it as a separate function and cross our fingers that all classes will implement it. + * See https://stackoverflow.com/questions/65846848/typescript-static-methods-in-interfaces for more information. + */ +export interface HasRaw { + /** + * Returns the raw counter part of the class type + */ + toRaw: () => R; +} + +/** + * Serializes a raw Zod type to a prettified JSON string + */ +export function serializeRaw(raw: z.AnyZodObject): string { + return JSON.stringify(raw, undefined, "\t"); +} + +/** + * Parses a JSON string according to a Zod schema + */ +export function deserializeRaw( + schema: z.ZodType, + json: string, +): z.infer { + const parse = schema.safeParse(JSON.parse(json)); + if (parse.success) { + return parse.data; + } else { + // TODO: implement some sort of user firendly error message + throw parse.error; + } +} + +/** + * + */ +export type idConverter = [RegExp, (number: Number) => string]; + +/** + * Converts a cystom string id + * @param matcher + */ +export function idFromRaw( + converter: idConverter, + raw: string | undefined, +): number | undefined { + if (!raw) return undefined; + + const match = raw.match(converter[0]); + if (!match?.groups?.id) return undefined; + + const number = parseInt(match.groups.id); + if (isNaN(number)) return undefined; + + return number; +} diff --git a/src/lib/classes/automaton/system/ComponentEdge.ts b/src/lib/classes/automaton/system/ComponentEdge.ts new file mode 100644 index 00000000..9e30d73d --- /dev/null +++ b/src/lib/classes/automaton/system/ComponentEdge.ts @@ -0,0 +1,42 @@ +import { AutomatonClass, type FromRaw } from "../AutomatonClass"; +import type { RawComponentEdge } from "./raw/RawComponentEdge"; + +/** + * Describes the edges of a system. + */ +export class ComponentEdge extends AutomatonClass { + constructor( + /** + * The beginning of the edge + */ + public parent: number, + + /** + * The end of the edge + */ + public child: number, + ) { + super(); + } + + /** + * Converts the SystemEdge to a RawSystemEdge + */ + toRaw() { + return { + parent: this.parent, + child: this.child, + }; + } + + /** + * Converts a RawSystemEdge to a SystemEdge + */ + static readonly fromRaw: FromRaw< + RawComponentEdge, + undefined, + ComponentEdge + > = (raw) => { + return new ComponentEdge(raw.parent, raw.child); + }; +} diff --git a/src/lib/classes/automaton/system/ComponentInstance.ts b/src/lib/classes/automaton/system/ComponentInstance.ts new file mode 100644 index 00000000..23cafd0a --- /dev/null +++ b/src/lib/classes/automaton/system/ComponentInstance.ts @@ -0,0 +1,60 @@ +import type { FromRaw } from "../AutomatonClass"; +import type { RawComponentInstance } from "./raw/RawComponentInstance"; +import { SystemMember } from "./SystemMember"; +import type { SystemMemberId } from "./SystemMemberId"; +import { Position } from "../Position"; + +const defaultX = 0; +const defaultY = 0; + +/** + * An instance of a Component + * This is used by a System to store what components are used + */ +export class ComponentInstance extends SystemMember { + constructor( + /** + * The id of the component that this is an instance of + * This must be unique and is shared by Operators + */ + public id: SystemMemberId, + + /** + * The name of the component this references + */ + public name: string, + + /** + * The position of the component this references + */ + public position: Position = new Position(defaultX, defaultY), + ) { + super(); + } + + /** + * Converts the ComponentInstance to a RawComponentInstance + */ + toRaw() { + return { + id: this.id.toRaw(), + componentName: this.name, + ...this.position.toRaw(), + }; + } + + /** + * Converts a RawComponentInstance to a ComponentInstance + */ + static readonly fromRaw: FromRaw< + RawComponentInstance, + { id: SystemMemberId }, + ComponentInstance + > = (raw, { id }) => { + return new ComponentInstance( + id, + raw.componentName, + Position.fromRaw({ x: raw.x ?? defaultX, y: raw.y ?? defaultY }), + ); + }; +} diff --git a/src/lib/classes/automaton/system/ComponentInstances.ts b/src/lib/classes/automaton/system/ComponentInstances.ts new file mode 100644 index 00000000..c65a66cf --- /dev/null +++ b/src/lib/classes/automaton/system/ComponentInstances.ts @@ -0,0 +1,66 @@ +import { IdScopedMap } from "../IdScopedMap"; +import type { RawNumberId } from "../raw/RawId"; +import type { SystemMember } from "./SystemMember"; +import type { SystemMemberId, SystemMemberIdInput } from "./SystemMemberId"; +import type { RawComponentInstance } from "./raw/RawComponentInstance"; +import type { RawComponentInstances } from "./raw/RawComponentInstances"; +import { ComponentInstance } from "./ComponentInstance"; +import type { IdMap } from "../IdMap"; +import type { FromRaw } from "../AutomatonClass"; + +export class ComponentInstances extends IdScopedMap< + SystemMember, + ComponentInstance, + SystemMemberId, + SystemMemberIdInput, + RawNumberId, + RawComponentInstances +> { + constructor( + map: IdMap< + SystemMember, + SystemMemberId, + SystemMemberIdInput, + RawNumberId, + any + >, + ) { + super(map, ComponentInstance); + } + + toRaw() { + return [...this].map((componentInstance) => componentInstance.toRaw()); + } + + static readonly fromRaw: FromRaw< + RawComponentInstances, + { + systemMembers: IdMap< + SystemMember, + SystemMemberId, + SystemMemberIdInput, + RawNumberId, + any + >; + }, + ComponentInstances + > = (raw, { systemMembers }) => { + const componentInstances = new ComponentInstances(systemMembers); + for (const rawComponentInstance of raw) { + const id = componentInstances.getNewIdFromRaw( + rawComponentInstance.id, + ); + + if (!id) + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Cannot load raw ComponentInstances where multiple id's are equivalent: ${rawComponentInstance.id}`, + ); + + componentInstances.add( + ComponentInstance.fromRaw(rawComponentInstance, { id }), + ); + } + return componentInstances; + }; +} diff --git a/src/lib/classes/automaton/system/Operator.ts b/src/lib/classes/automaton/system/Operator.ts new file mode 100644 index 00000000..84cf0f92 --- /dev/null +++ b/src/lib/classes/automaton/system/Operator.ts @@ -0,0 +1,53 @@ +import type { FromRaw } from "../AutomatonClass"; +import type { RawOperator } from "./raw/RawOperator"; +import { OperatorType } from "./OperatorType"; +import { Position } from "../Position"; +import type { SystemMemberId } from "./SystemMemberId"; +import { SystemMember } from "./SystemMember"; + +/** + * Defines an operator for a System + */ +export class Operator extends SystemMember { + constructor( + /** + * The id of the operator + * must be unique and matches the id of a ComponentInstance + */ + public id: SystemMemberId, + + /** + * The type of operator + */ + public type: OperatorType = OperatorType.COMPOSITION, + + /** + * The position of the operator + */ + readonly position: Position = new Position(0, 0), + ) { + super(); + } + + /** + * Converts the Operator to a RawOperator + */ + toRaw() { + return { + id: this.id.toRaw(), + type: this.type, + ...this.position.toRaw(), + }; + } + + /** + * Converts a RawOperator to an Operator + */ + static readonly fromRaw: FromRaw< + RawOperator, + { id: SystemMemberId }, + Operator + > = (raw, { id }) => { + return new Operator(id, raw.type, Position.fromRaw(raw)); + }; +} diff --git a/src/lib/classes/automaton/OperatorType.ts b/src/lib/classes/automaton/system/OperatorType.ts similarity index 97% rename from src/lib/classes/automaton/OperatorType.ts rename to src/lib/classes/automaton/system/OperatorType.ts index 6d365759..dcdf84bf 100644 --- a/src/lib/classes/automaton/OperatorType.ts +++ b/src/lib/classes/automaton/system/OperatorType.ts @@ -1,7 +1,7 @@ /** * The operator types supported by Ecdar * The values are lower case because of compatibility - * */ + */ export enum OperatorType { COMPOSITION = "composition", CONJUNCTION = "conjunction", diff --git a/src/lib/classes/automaton/system/Operators.ts b/src/lib/classes/automaton/system/Operators.ts new file mode 100644 index 00000000..7acb8646 --- /dev/null +++ b/src/lib/classes/automaton/system/Operators.ts @@ -0,0 +1,62 @@ +import { IdScopedMap } from "../IdScopedMap"; +import type { RawNumberId } from "../raw/RawId"; +import type { SystemMember } from "./SystemMember"; +import type { SystemMemberId, SystemMemberIdInput } from "./SystemMemberId"; +import type { RawOperator } from "./raw/RawOperator"; +import type { RawOperators } from "./raw/RawOperators"; +import { Operator } from "./Operator"; +import type { IdMap } from "../IdMap"; +import type { FromRaw } from "../AutomatonClass"; + +export class Operators extends IdScopedMap< + SystemMember, + Operator, + SystemMemberId, + SystemMemberIdInput, + RawNumberId, + RawOperators +> { + constructor( + map: IdMap< + SystemMember, + SystemMemberId, + SystemMemberIdInput, + RawNumberId, + any + >, + ) { + super(map, Operator); + } + + toRaw() { + return [...this].map((operator) => operator.toRaw()); + } + + static readonly fromRaw: FromRaw< + RawOperators, + { + systemMembers: IdMap< + SystemMember, + SystemMemberId, + SystemMemberIdInput, + RawNumberId, + any + >; + }, + Operators + > = (raw, { systemMembers }) => { + const operators = new Operators(systemMembers); + for (const rawOperator of raw) { + const id = operators.getNewIdFromRaw(rawOperator.id); + + if (!id) + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Cannot load raw Operators where multiple id's are equivalent: ${rawOperator.id}`, + ); + + operators.add(Operator.fromRaw(rawOperator, { id })); + } + return operators; + }; +} diff --git a/src/lib/classes/automaton/system/System.ts b/src/lib/classes/automaton/system/System.ts new file mode 100644 index 00000000..8c9e4de4 --- /dev/null +++ b/src/lib/classes/automaton/system/System.ts @@ -0,0 +1,192 @@ +import { AutomatonClass, type FromRaw } from "../AutomatonClass"; +import type { RawSystem } from "./raw/RawSystem"; +import type { HasId } from "../HasId"; +import type { SystemId } from "./SystemId"; +import { Position } from "../Position"; +import { Dimensions } from "../Dimensions"; +import { SystemMemberEdge } from "./SystemMemberEdge"; +import { SystemMembers } from "./SystemMembers"; +import { ComponentInstances } from "./ComponentInstances"; +import { Operators } from "./Operators"; + +/** + * An Ecdar System + */ +export class System + extends AutomatonClass + implements HasId +{ + constructor( + /** + * The name of the system + */ + public id: SystemId, + + /** + * The description of the system + */ + public description: string = "", + + /** + * The position of the system + */ + public position: Position = new Position(0, 0), + + /** + * The dimensions of the system + */ + public dimensions: Dimensions = new Dimensions(0, 0), + + /** + * The color of the system + */ + public color: string = "", + + /** + * The coordinate of root of the system + */ + public systemRootX: number = 0, + + /** + * A list of members in the system. + * Members can be component instances or operators. They all share the same unique ID's to allow them to be connected with edges. + */ + members: SystemMembers | undefined, + + /** + * A list of component instances in the system + */ + componentInstances: ComponentInstances | undefined, + + /** + * A list of operators in the system + */ + operators: Operators | undefined, + + /** + * A list of edges in the system + */ + public edges: SystemMemberEdge[] = [], + ) { + super(); + + this.#members = members || new SystemMembers(); + this.#componentInstances = + componentInstances || new ComponentInstances(this.members); + this.#operators = operators || new Operators(this.members); + this.membersCheck(); + } + + /** + * A list of members in the system. + * Members can be component instances or operators. They all share the same unique ID's to allow them to be connected with edges. + */ + get members() { + return this.#members; + } + set members(value) { + this.#members = value; + this.membersCheck(); + } + #members!: SystemMembers; + + /** + * A list of component instances in the system + */ + get componentInstances() { + return this.#componentInstances; + } + set componentInstances(value) { + this.#componentInstances = value; + this.membersCheck(); + } + #componentInstances!: ComponentInstances; + + /** + * A list of operators in the system + */ + get operators() { + return this.#operators; + } + set operators(value) { + this.#operators = value; + this.membersCheck(); + } + #operators!: Operators; + + /** + * Ensures that the scoped stores are all based on the main `members` store. + */ + private membersCheck() { + if (this.componentInstances.map !== this.members) { + throw new TypeError( + "The component instances map must be based on the members map.", + ); + } + if (this.operators.map !== this.members) { + throw new TypeError( + "The operators map must be based on the members map.", + ); + } + } + + /** + * Converts the System to a RawSystem + */ + toRaw() { + return { + name: this.id.toRaw(), + description: this.description, + ...this.position.toRaw(), + ...this.dimensions.toRaw(), + color: this.color, + systemRootX: this.systemRootX, + componentInstances: this.componentInstances.toRaw(), + operators: this.operators.toRaw(), + edges: this.edges.map((edge) => edge.toRaw()), + }; + } + + /** + * Converts a RawSystem to a System + */ + static readonly fromRaw: FromRaw = ( + raw, + { id }, + ) => { + const members = new SystemMembers(); + return new System( + id, + raw.description, + Position.fromRaw(raw), + Dimensions.fromRaw(raw), + raw.color, + raw.systemRootX, + members, + ComponentInstances.fromRaw(raw.componentInstances, { + systemMembers: members, + }), + Operators.fromRaw(raw.operators, { systemMembers: members }), + raw.edges.map((rawSystemEdge) => { + const parent = members.getId(rawSystemEdge.parent); + if (!parent) { + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Cannot generate an edge where the parent doesn't exist: ${rawSystemEdge.parent}`, + ); + } + const child = members.getId(rawSystemEdge.child); + if (!child) { + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Cannot generate an edge where the child doesn't exist: ${rawSystemEdge.child}`, + ); + } + return SystemMemberEdge.fromRaw(rawSystemEdge, { + parent, + child, + }); + }), + ); + }; +} diff --git a/src/lib/classes/automaton/system/SystemId.ts b/src/lib/classes/automaton/system/SystemId.ts new file mode 100644 index 00000000..fc167b84 --- /dev/null +++ b/src/lib/classes/automaton/system/SystemId.ts @@ -0,0 +1,8 @@ +import { Id } from "../Id"; +import type { RawId, RawStringId } from "../raw/RawId"; + +export class SystemId extends Id { + parse(rawId: string) { + return { rawId, order: undefined, orders: undefined }; + } +} diff --git a/src/lib/classes/automaton/system/SystemMember.ts b/src/lib/classes/automaton/system/SystemMember.ts new file mode 100644 index 00000000..ff8c5556 --- /dev/null +++ b/src/lib/classes/automaton/system/SystemMember.ts @@ -0,0 +1,10 @@ +import { AutomatonClass } from "../AutomatonClass"; +import type { HasId } from "../HasId"; +import type { SystemMemberId } from "./SystemMemberId"; + +export abstract class SystemMember + extends AutomatonClass + implements HasId +{ + abstract id: SystemMemberId; +} diff --git a/src/lib/classes/automaton/system/SystemMemberEdge.ts b/src/lib/classes/automaton/system/SystemMemberEdge.ts new file mode 100644 index 00000000..51c803c2 --- /dev/null +++ b/src/lib/classes/automaton/system/SystemMemberEdge.ts @@ -0,0 +1,43 @@ +import { AutomatonClass, type FromRaw } from "../AutomatonClass"; +import type { SystemMemberId } from "./SystemMemberId"; +import type { RawComponentEdge } from "./raw/RawComponentEdge"; + +/** + * Describes the edges of a system. + */ +export class SystemMemberEdge extends AutomatonClass { + constructor( + /** + * The beginning of the edge + */ + public parent: SystemMemberId, + + /** + * The end of the edge + */ + public child: SystemMemberId, + ) { + super(); + } + + /** + * Converts the SystemEdge to a RawSystemEdge + */ + toRaw() { + return { + parent: this.parent.toRaw(), + child: this.child.toRaw(), + }; + } + + /** + * Converts a RawSystemEdge to a SystemEdge + */ + static readonly fromRaw: FromRaw< + RawComponentEdge, + { parent: SystemMemberId; child: SystemMemberId }, + SystemMemberEdge + > = (raw, { parent, child }) => { + return new SystemMemberEdge(parent, child); + }; +} diff --git a/src/lib/classes/automaton/system/SystemMemberId.ts b/src/lib/classes/automaton/system/SystemMemberId.ts new file mode 100644 index 00000000..e868b14a --- /dev/null +++ b/src/lib/classes/automaton/system/SystemMemberId.ts @@ -0,0 +1,10 @@ +import { Id } from "../Id"; +import type { RawNumberId } from "../raw/RawId"; + +export type SystemMemberIdInput = number | RawNumberId; + +export class SystemMemberId extends Id { + parse(id: SystemMemberIdInput) { + return { rawId: id, order: id, orders: undefined }; + } +} diff --git a/src/lib/classes/automaton/system/SystemMembers.ts b/src/lib/classes/automaton/system/SystemMembers.ts new file mode 100644 index 00000000..8f918257 --- /dev/null +++ b/src/lib/classes/automaton/system/SystemMembers.ts @@ -0,0 +1,40 @@ +import type { FromRaw } from "../AutomatonClass"; +import { IdMap } from "../IdMap"; +import type { RawNumberId } from "../raw/RawId"; +import { ComponentInstance } from "./ComponentInstance"; +import { Operator } from "./Operator"; +import type { SystemMember } from "./SystemMember"; +import { SystemMemberId, type SystemMemberIdInput } from "./SystemMemberId"; +import type { RawComponentInstance } from "./raw/RawComponentInstance"; +import type { RawOperator } from "./raw/RawOperator"; +import type { RawSystemMembers } from "./raw/RawSystemMembers"; + +export class SystemMembers extends IdMap< + SystemMember, + SystemMemberId, + SystemMemberIdInput, + RawNumberId, + RawSystemMembers +> { + constructor() { + super(SystemMemberId); + } + + toRaw() { + const raw: { + componentInstances: RawComponentInstance[]; + operators: RawOperator[]; + } = { + componentInstances: [], + operators: [], + }; + for (const member of this) { + if (member instanceof ComponentInstance) { + raw.componentInstances.push(member.toRaw()); + } else if (member instanceof Operator) { + raw.operators.push(member.toRaw()); + } + } + return raw; + } +} diff --git a/src/lib/classes/automaton/system/Systems.ts b/src/lib/classes/automaton/system/Systems.ts new file mode 100644 index 00000000..f073e671 --- /dev/null +++ b/src/lib/classes/automaton/system/Systems.ts @@ -0,0 +1,40 @@ +import type { FromRaw } from "../AutomatonClass"; +import { IdMap } from "../IdMap"; +import type { RawStringId } from "../raw/RawId"; +import { System } from "./System"; +import { SystemId } from "./SystemId"; +import type { RawSystems } from "./raw/RawSystems"; + +export class Systems extends IdMap< + System, + SystemId, + string, + RawStringId, + RawSystems +> { + constructor() { + super(SystemId); + } + + toRaw() { + return [...this].map((system) => system.toRaw()); + } + + static readonly fromRaw: FromRaw = ( + raw, + ) => { + const systems = new Systems(); + for (const rawSystem of raw) { + const id = systems.getNewIdFromRaw(rawSystem.name); + + if (!id) + //TODO: Make this a user-friendly message with different options for recovering + throw new TypeError( + `Cannot load raw Systems where multiple names are equivalent: ${rawSystem.name}`, + ); + + systems.add(System.fromRaw(rawSystem, { id })); + } + return systems; + }; +} diff --git a/src/lib/classes/automaton/system/raw/RawComponentEdge.ts b/src/lib/classes/automaton/system/raw/RawComponentEdge.ts new file mode 100644 index 00000000..d3bd10d8 --- /dev/null +++ b/src/lib/classes/automaton/system/raw/RawComponentEdge.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +/** + * Used to parse a RawComponentEdge through Zod + */ +export const ZodRawComponentEdge = z.object({ + child: z.number(), + parent: z.number(), +}); + +export type RawComponentEdge = z.infer; diff --git a/src/lib/classes/automaton/system/raw/RawComponentInstance.ts b/src/lib/classes/automaton/system/raw/RawComponentInstance.ts new file mode 100644 index 00000000..ee12f08a --- /dev/null +++ b/src/lib/classes/automaton/system/raw/RawComponentInstance.ts @@ -0,0 +1,13 @@ +import { z } from "zod"; +import { ZodRawPosition } from "../../raw/RawPosition"; + +/** + * Used to parse a RawComponentInstance through Zod + */ +export const ZodRawComponentInstance = z.object({ + id: z.number(), + componentName: z.string(), + ...ZodRawPosition.partial().shape, +}); + +export type RawComponentInstance = z.infer; diff --git a/src/lib/classes/automaton/system/raw/RawComponentInstances.ts b/src/lib/classes/automaton/system/raw/RawComponentInstances.ts new file mode 100644 index 00000000..9f152511 --- /dev/null +++ b/src/lib/classes/automaton/system/raw/RawComponentInstances.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; +import { ZodRawComponentInstance } from "./RawComponentInstance"; + +/** + * Used to parse a RawComponentInstances through Zod + */ +export const ZodRawComponentInstances = z.array(ZodRawComponentInstance); + +/** + * The raw Object for a ComponentInstances that is used to save and communicate in JSON. + */ +export type RawComponentInstances = z.infer; diff --git a/src/lib/classes/automaton/raw/RawOperator.ts b/src/lib/classes/automaton/system/raw/RawOperator.ts similarity index 76% rename from src/lib/classes/automaton/raw/RawOperator.ts rename to src/lib/classes/automaton/system/raw/RawOperator.ts index 452b10ea..6869fb75 100644 --- a/src/lib/classes/automaton/raw/RawOperator.ts +++ b/src/lib/classes/automaton/system/raw/RawOperator.ts @@ -1,14 +1,14 @@ import { z } from "zod"; import { OperatorType } from "../OperatorType"; +import { ZodRawPosition } from "../../raw/RawPosition"; /** * Used to parse a RawOperator through Zod - * */ + */ export const ZodRawOperator = z.object({ id: z.number(), type: z.nativeEnum(OperatorType), - x: z.number(), - y: z.number(), + ...ZodRawPosition.shape, }); export type RawOperator = z.infer; diff --git a/src/lib/classes/automaton/system/raw/RawOperators.ts b/src/lib/classes/automaton/system/raw/RawOperators.ts new file mode 100644 index 00000000..2dbfee28 --- /dev/null +++ b/src/lib/classes/automaton/system/raw/RawOperators.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; +import { ZodRawOperator } from "./RawOperator"; + +/** + * Used to parse a RawOperators through Zod + */ +export const ZodRawOperators = z.array(ZodRawOperator); + +/** + * The raw Object for a Operators that is used to save and communicate in JSON. + */ +export type RawOperators = z.infer; diff --git a/src/lib/classes/automaton/raw/RawSystem.ts b/src/lib/classes/automaton/system/raw/RawSystem.ts similarity index 62% rename from src/lib/classes/automaton/raw/RawSystem.ts rename to src/lib/classes/automaton/system/raw/RawSystem.ts index d34f8a58..68807cdd 100644 --- a/src/lib/classes/automaton/raw/RawSystem.ts +++ b/src/lib/classes/automaton/system/raw/RawSystem.ts @@ -1,26 +1,26 @@ import { z } from "zod"; +import { ZodRawPosition } from "../../raw/RawPosition"; +import { ZodRawDimensions } from "../../raw/RawDimensions"; import { ZodRawComponentInstance } from "./RawComponentInstance"; import { ZodRawOperator } from "./RawOperator"; -import { ZodRawSystemEdge } from "./RawSystemEdge"; +import { ZodRawComponentEdge } from "./RawComponentEdge"; /** * Used to parse a RawSystem through Zod - * */ + */ export const ZodRawSystem = z.object({ name: z.string(), description: z.string(), - x: z.number(), - y: z.number(), - width: z.number(), - height: z.number(), + ...ZodRawPosition.shape, + ...ZodRawDimensions.shape, color: z.string(), - systemRootX: z.number(), + systemRootX: ZodRawPosition.shape.x, componentInstances: ZodRawComponentInstance.array(), operators: ZodRawOperator.array(), - edges: ZodRawSystemEdge.array(), + edges: ZodRawComponentEdge.array(), }); /** * The raw Object for a System that is used to save and communicate in JSON. - * */ + */ export type RawSystem = z.infer; diff --git a/src/lib/classes/automaton/system/raw/RawSystemMembers.ts b/src/lib/classes/automaton/system/raw/RawSystemMembers.ts new file mode 100644 index 00000000..c5e5c46f --- /dev/null +++ b/src/lib/classes/automaton/system/raw/RawSystemMembers.ts @@ -0,0 +1,18 @@ +import { z } from "zod"; +import { ZodRawComponentInstances } from "./RawComponentInstances"; +import { ZodRawOperators } from "./RawOperators"; + +/** + * Used to parse a RawSystemMembers through Zod + */ +export const ZodRawSystemMembers = z + .object({ + componentInstances: ZodRawComponentInstances, + operators: ZodRawOperators, + }) + .partial(); + +/** + * The raw Object for a SystemMembers that is used to save and communicate in JSON. + */ +export type RawSystemMembers = z.infer; diff --git a/src/lib/classes/automaton/system/raw/RawSystems.ts b/src/lib/classes/automaton/system/raw/RawSystems.ts new file mode 100644 index 00000000..49ccc7d3 --- /dev/null +++ b/src/lib/classes/automaton/system/raw/RawSystems.ts @@ -0,0 +1,12 @@ +import { z } from "zod"; +import { ZodRawSystem } from "./RawSystem"; + +/** + * Used to parse a RawSystems through Zod + */ +export const ZodRawSystems = z.array(ZodRawSystem); + +/** + * The raw Object for a Systems that is used to save and communicate in JSON. + */ +export type RawSystems = z.infer; diff --git a/src/lib/classes/draw.ts b/src/lib/classes/draw.ts deleted file mode 100644 index f3554afc..00000000 --- a/src/lib/classes/draw.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { Point } from "./draw/Point"; -export type { PointLike } from "./draw/Point"; -export { Dimensions } from "./draw/Dimensions"; diff --git a/src/lib/classes/draw/Dimensions.ts b/src/lib/classes/draw/Dimensions.ts deleted file mode 100644 index 5945adcf..00000000 --- a/src/lib/classes/draw/Dimensions.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Describes the width and height of an object - * */ -export class Dimensions { - width: number = 0; - height: number = 0; - - constructor(width: number, height: number) { - this.width = width; - this.height = height; - } -} diff --git a/src/lib/classes/draw/Point.ts b/src/lib/classes/draw/Point.ts deleted file mode 100644 index 2460f826..00000000 --- a/src/lib/classes/draw/Point.ts +++ /dev/null @@ -1,30 +0,0 @@ -export interface PointLike { - x: number; - y: number; -} - -/** - * Describes a point in a coordinate system - * */ -export class Point implements PointLike { - #x: number; - #y: number; - - get x() { - return this.#x; - } - set x(value: number) { - this.#x = value; - } - get y() { - return this.#y; - } - set y(value: number) { - this.#y = value; - } - - constructor(x: number, y: number) { - this.#x = x; - this.#y = y; - } -} diff --git a/src/lib/classes/fileAdapter/FileAdapter.ts b/src/lib/classes/fileAdapter/FileAdapter.ts index d2268784..cdc7b325 100644 --- a/src/lib/classes/fileAdapter/FileAdapter.ts +++ b/src/lib/classes/fileAdapter/FileAdapter.ts @@ -1,4 +1,4 @@ -import type { Project } from "../project/Project"; +import type { Project } from "../automaton/Project"; import type { IFileAdapterImplementation } from "./FileAdapterImplementation"; import { FileAdapterTauri } from "./FileAdapterTauri"; import { FileAdapterFallback } from "./FileAdapterFallback"; diff --git a/src/lib/classes/project/Project.ts b/src/lib/classes/project/Project.ts deleted file mode 100644 index 7a4813c9..00000000 --- a/src/lib/classes/project/Project.ts +++ /dev/null @@ -1,80 +0,0 @@ -import * as Automata from "../automaton"; - -export const PROJECT_FOLDER_NAME_SYSTEMS = "Systems"; -export const PROJECT_FOLDER_NAME_COMPONENTS = "Components"; -export const PROJECT_FILE_NAME_QUERIES = "Queries.json"; -export const PROJECT_FILE_NAME_SYSTEM_DECLARATIONS = "SystemDeclarations.json"; -export const PROJECT_FILE_NAME_GLOBAL_DECLARATIONS = "GlobalDeclarations.json"; - -export class Project implements Automata.Named { - /** - * The name of the project, and the name of the save folder - * */ - name: string; - - /** - * All components in the project - * */ - components: Automata.Component[]; - - /** - * All systems in the project - * */ - systems: Automata.System[]; - - /** - * All queries of the project - * */ - queries: Automata.Queries; - - /** - * The system declaration of the project - * */ - systemDeclarations: Automata.Declaration; - - /** - * The global declarations of the project - * */ - globalDeclarations: Automata.Declaration; - - constructor( - name = "New Project", - components: Automata.Component[] = [], - systems: Automata.System[] = [], - queries = new Automata.Queries(), - systemDeclarations = new Automata.Declaration( - Automata.DeclarationType.SYSTEM, - ), - globalDeclarations = new Automata.Declaration( - Automata.DeclarationType.GLOBAL, - ), - ) { - this.name = name; - this.components = components; - this.systems = systems; - this.queries = queries; - this.systemDeclarations = systemDeclarations; - this.globalDeclarations = globalDeclarations; - } - - /** - * Creates a new project - * */ - static create( - name?: string, - components?: Automata.Component[], - systems?: Automata.System[], - queries?: Automata.Queries, - systemDeclarations?: Automata.Declaration, - globalDeclarations?: Automata.Declaration, - ) { - return new Project( - name, - components, - systems, - queries, - systemDeclarations, - globalDeclarations, - ); - } -} diff --git a/src/lib/components/contextMenu/contextMenu.ts b/src/lib/components/contextMenu/contextMenu.ts index 66c22f9a..fc2a6f10 100644 --- a/src/lib/components/contextMenu/contextMenu.ts +++ b/src/lib/components/contextMenu/contextMenu.ts @@ -1,5 +1,5 @@ import type { ComponentType } from "svelte"; -import type { PointLike } from "$lib/classes/draw"; +import type { IPoint } from "$lib/classes/draw"; import { content as contentStore, anchor, open } from "./state"; import { PointElement } from "../overlayMenu/PointElement"; import type { ContextMenuProps } from "./ContextMenuProps"; @@ -32,7 +32,7 @@ export function contextMenu( } function openOverlayMenu( - point: PointLike, + point: IPoint, component: ComponentType, props: ContextMenuProps, ) { diff --git a/src/lib/components/overlayMenu/OverlayMenu.svelte b/src/lib/components/overlayMenu/OverlayMenu.svelte index a2d55542..79f95bc9 100644 --- a/src/lib/components/overlayMenu/OverlayMenu.svelte +++ b/src/lib/components/overlayMenu/OverlayMenu.svelte @@ -1,7 +1,7 @@ diff --git a/src/lib/components/query/Queries.svelte b/src/lib/components/query/Queries.svelte index d3e8192e..a11baace 100644 --- a/src/lib/components/query/Queries.svelte +++ b/src/lib/components/query/Queries.svelte @@ -11,14 +11,13 @@ } -{#each $queries?.arr || [] as query, index} +{#each $queries || [] as query, index} {/each} diff --git a/src/lib/components/query/Query.svelte b/src/lib/components/query/Query.svelte index 2f35bebd..7096864f 100644 --- a/src/lib/components/query/Query.svelte +++ b/src/lib/components/query/Query.svelte @@ -13,7 +13,8 @@ export let type: string; export let name: string; export let isPeriodic: boolean; - export let backend: Backend; + // TODO: pipe this in + let backend: 0; export let index: number; export let comment: string = "Comment"; export let color: string = "var(--queries-element-color)"; diff --git a/src/lib/components/query/QueryDropDownMenu.svelte b/src/lib/components/query/QueryDropDownMenu.svelte index 715a4d42..767288cc 100644 --- a/src/lib/components/query/QueryDropDownMenu.svelte +++ b/src/lib/components/query/QueryDropDownMenu.svelte @@ -50,7 +50,7 @@ icon={Delete} text="Delete" click={() => { - $queries?.arr.splice(index, 1); + $queries?.splice(index, 1); $queries = $queries; }} /> diff --git a/src/lib/components/query/QueryNav.svelte b/src/lib/components/query/QueryNav.svelte index d2522dd8..e63022d1 100644 --- a/src/lib/components/query/QueryNav.svelte +++ b/src/lib/components/query/QueryNav.svelte @@ -1,13 +1,11 @@ diff --git a/src/lib/components/startScreen/StartScreen.svelte b/src/lib/components/startScreen/StartScreen.svelte index 1ceae4c6..09991f39 100644 --- a/src/lib/components/startScreen/StartScreen.svelte +++ b/src/lib/components/startScreen/StartScreen.svelte @@ -1,9 +1,10 @@ diff --git a/src/lib/components/svg-view/Edge.svelte b/src/lib/components/svg-view/Edge.svelte index a01b2c6a..f8dd7714 100644 --- a/src/lib/components/svg-view/Edge.svelte +++ b/src/lib/components/svg-view/Edge.svelte @@ -1,26 +1,29 @@ - - {#each filterSystemEdges($activeView?.edges) as edge} - {#if "sourceLocation" in edge} + {#if $activeView instanceof Component} + {@const component = $activeView} + + {#each $activeView.edges as edge} - {/if} - {/each} + {/each} - - {#each locationsAsArray($activeView) as location} - - {/each} + + {#each $activeView.locations as location} + + {/each} + {:else if $activeView instanceof System} + TODO: Not implemented yet + {/if} diff --git a/src/lib/globalState/activeModel.ts b/src/lib/globalState/activeModel.ts deleted file mode 100644 index 7e56a72f..00000000 --- a/src/lib/globalState/activeModel.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { writable, type Writable } from "svelte/store"; -import type { iLocation } from "$lib/interfaces/iLocation"; -import type { iEdge } from "$lib/interfaces/iEdge"; -import { - LocationType, - PropertyType, - Status, - Urgency, -} from "$lib/classes/automaton"; - -export class ActiveModel { - constructor( - private _locations: Record = {}, - private _edges: iEdge[] = [], - ) {} - - get locations() { - return this._locations; - } - - get edges() { - return this._edges; - } -} - -export const activeModel: Writable = writable(new ActiveModel()); - -// TODO: This is just adding temporary data -activeModel.set( - new ActiveModel( - { - "1": { - color: "#ff0000", - id: "1", - invariant: { fn: "c >= 8", position: { x: 100, y: 300 } }, - nickname: { name: "nickname", position: { x: 100, y: 300 } }, - position: { x: 100, y: 300 }, - type: LocationType.INITIAL, - urgency: Urgency.NORMAL, - }, - "2": { - color: "#ff0000", - id: "2", - invariant: { fn: "c >= 8", position: { x: 300, y: 300 } }, - nickname: { name: "nickname", position: { x: 300, y: 300 } }, - position: { x: 300, y: 300 }, - type: LocationType.INITIAL, - urgency: Urgency.NORMAL, - }, - "3": { - color: "#00ff00", // Example color for location 3 - id: "3", - invariant: { fn: "c >= 8", position: { x: 500, y: 500 } }, - nickname: { - name: "Location numba drei", - position: { x: 500, y: 500 }, - }, // Example nickname - position: { x: 500, y: 500 }, // Example position - type: LocationType.NORMAL, // Example location type - urgency: Urgency.NORMAL, // Example urgency - }, - "4": { - color: "#0000ff", // Example color for location 4 - id: "4", - invariant: { fn: "c >= 8", position: { x: 700, y: 320 } }, - nickname: { - name: "nickname4laefpefleapflp", - position: { x: 700, y: 320 }, - }, // Example nickname - position: { x: 700, y: 300 }, // Example position - type: LocationType.INITIAL, // Example location type - urgency: Urgency.NORMAL, // Example urgency - }, - }, - - [ - { - guard: "c >= 8", - id: "1", - nails: [], - sourceLocation: "1", - targetLocation: "2", - status: Status.INPUT, - sync: "", - update: "", - }, - { - guard: "c >= 8", - id: "1", - nails: [], - sourceLocation: "1", - targetLocation: "2", - status: Status.INPUT, - sync: "", - update: "", - }, - // Add more edge objects here for additional connections - { - guard: "c < 8", - id: "2", - sourceLocation: "1", - targetLocation: "3", - status: Status.OUTPUT, - sync: "", - update: "", - nails: [ - { - position: { x: 350, y: 300 }, - property: { - type: PropertyType.SYNCHRONIZATION, - position: { - x: 350, - y: 300, - }, - }, - }, - ], - }, - { - guard: "c >= 10", - id: "3", - nails: [], - sourceLocation: "3", - targetLocation: "4", - status: Status.OUTPUT, - sync: "", - update: "", - }, - ], - ), -); diff --git a/src/lib/globalState/activeProject.ts b/src/lib/globalState/activeProject.ts index ce859f54..757cd8b8 100644 --- a/src/lib/globalState/activeProject.ts +++ b/src/lib/globalState/activeProject.ts @@ -29,6 +29,8 @@ import type { Component, System } from "$lib/classes/automaton"; type ProjectKeys = Pick< Project, | "id" + | "locationIds" + | "edgeIds" | "components" | "systems" | "queries" @@ -43,6 +45,8 @@ const memberSubscribers: { [key in keyof ProjectKeys]: Set>; } = { id: new Set(), + locationIds: new Set(), + edgeIds: new Set(), components: new Set(), systems: new Set(), queries: new Set(), @@ -58,7 +62,7 @@ const memberSubscribers: { */ export const project: Writable = { set(value) { - projectStore = value; + projectValue = value; for (const subscriber of projectSubscribers) { subscriber(projectValue); } @@ -139,6 +143,16 @@ class ProjectMemberStore */ export const name = new ProjectMemberStore("id"); +/** + * This store subscribes to changes / can make changes on the `locationIds` member of the active `Project`. + */ +export const locationIds = new ProjectMemberStore("locationIds"); + +/** + * This store subscribes to changes / can make changes on the `edgeIds` member of the active `Project`. + */ +export const edgeIds = new ProjectMemberStore("edgeIds"); + /** * This store subscribes to changes / can make changes on the `components` member of the active `Project`. */ diff --git a/src/tests/lib/classes/automaton/success.test.ts b/src/tests/lib/classes/automaton/success.test.ts index e84055e5..53f6fc67 100644 --- a/src/tests/lib/classes/automaton/success.test.ts +++ b/src/tests/lib/classes/automaton/success.test.ts @@ -3131,8 +3131,3 @@ const projectData = JSON.stringify({ declarations: "broadcast chan pub, grant, patent, coin, tea, cof;", }, }); - -/* - - -*/ From 8f702d9c71fa1adeb151bd32e3df60e4352a25aa Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 13 Nov 2023 15:18:37 +0100 Subject: [PATCH 07/48] Minor fix fix --- src/lib/components/project/ProjectItemDropDownMenu.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/components/project/ProjectItemDropDownMenu.svelte b/src/lib/components/project/ProjectItemDropDownMenu.svelte index 9b5ccbff..06c3187c 100644 --- a/src/lib/components/project/ProjectItemDropDownMenu.svelte +++ b/src/lib/components/project/ProjectItemDropDownMenu.svelte @@ -31,7 +31,7 @@ "brown", ]; - const menuId = `${itemType}-menu-${id.rawId}`; + const menuId = `${itemType}-menu-${id?.rawId}`; let button: HTMLElement; function togglePeriodicCheck(event: MouseEvent) { @@ -44,7 +44,7 @@ class="dropdown" bind:this={button} popovertarget={menuId} - id={`${itemType}-button-${id.rawId}`} + id={`${itemType}-button-${id?.rawId}`} > From 21262f7a389aea7f724aa3c13d3da34402cd9a93 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 13 Nov 2023 17:06:37 +0100 Subject: [PATCH 08/48] More plumbing --- src/lib/components/project/ProjectItem.svelte | 33 +++++---- .../components/project/ProjectItems.svelte | 74 ++++++++++++++----- src/lib/components/query/Query.svelte | 2 +- src/lib/components/svg-view/Label.svelte | 12 +-- src/lib/components/svg-view/Location.svelte | 2 +- 5 files changed, 79 insertions(+), 44 deletions(-) diff --git a/src/lib/components/project/ProjectItem.svelte b/src/lib/components/project/ProjectItem.svelte index 4745d77f..72e6a91a 100644 --- a/src/lib/components/project/ProjectItem.svelte +++ b/src/lib/components/project/ProjectItem.svelte @@ -3,27 +3,26 @@ Folder_special, Request_page, } from "svelte-google-materialdesign-icons"; + import type { SystemId } from "$lib/classes/automaton/system/SystemId"; + import type { ComponentId } from "$lib/classes/automaton/component/ComponentId"; import ProjectItemDropDownMenu from "./ProjectItemDropDownMenu.svelte"; - export let name: string; + export let id: SystemId | ComponentId; export let description: string; export let color: string; export let includeInPeriodicCheck: boolean = false; - export let index: number; export let itemType: "system" | "component"; - function handleDoubleClick() { - name = prompt("New name:", name) || name; - } + export let setAsActive: () => void; + export let rename: () => void; -
-
+
+