Explorar o código

WIP: Parser seems to be working in GUI

Joeri Exelmans %!s(int64=2) %!d(string=hai) anos
pai
achega
abca3ccb0f

+ 21 - 23
src/frontend/app.tsx

@@ -6,7 +6,7 @@ import {IconPlayerTrackPrev, IconPlayerTrackNext, IconInfoCircle} from "@tabler/
 
 import {GraphDeltaExecutor} from "../onion/delta_executor"; 
 import {CompositeDelta, CompositeLevel} from "../onion/composite_delta";
-import {Version, initialVersion, VersionRegistry} from "../onion/version";
+import {embed, Version, initialVersion, VersionRegistry} from "../onion/version";
 // import {NodeCreation, NodeDeletion, EdgeCreation, EdgeUpdate} from "../onion/primitive_delta";
 import {PrimitiveDelta} from "../onion/primitive_delta";
 import {PrimitiveValue, UUID} from "../onion/types";
@@ -226,7 +226,7 @@ class VersionedModel extends React.Component<VersionedModelProps, {}> {
     }
 
     const makeConcreteSyntaxTabs = defaultTab => (
-      <Tabs defaultValue={defaultTab}>
+      <Tabs defaultValue={defaultTab} keepMounted={false}>
         {this.props.readonly?(
           <Tabs.List>
             <Tabs.Tab value="state">State</Tabs.Tab>
@@ -377,7 +377,7 @@ export class App extends React.Component<{}, AppState> {
       as: makeModelState(this.setAsState.bind(this)),
     };
 
-    this.parser = new TrivialParser(this.generateUUID, this.state.corr.versionRegistry,
+    this.parser = new TrivialParser(this.generateUUID,
       {
         csLvl: this.state.cs.compositeLevel,
         asLvl: this.state.as.compositeLevel,
@@ -386,18 +386,17 @@ export class App extends React.Component<{}, AppState> {
   }
 
   render() {
-    // const execChanges = ({version, historyGraph, dependencyGraphL0, dependencyGraphL1, ...rest}: VersionedModelState, newVersion: Version, composite: CompositeDelta) => {
-    const execChanges = ({version, historyGraph, dependencyGraphL1, dependencyGraphL0, ...rest}: VersionedModelState, newVersion: Version, composite: CompositeDelta) => {
+    const addVersionAndDeltas = ({version, historyGraph, dependencyGraphL1, dependencyGraphL0, ...rest}: VersionedModelState, newVersion: Version, composite: CompositeDelta) => {
       return {
         version: newVersion,
 
-        // // add new version to history graph + highlight the new version as the current version:
+        // add new version to history graph + highlight the new version as the current version:
         historyGraph: setCurrentVersion(appendToHistoryGraph(historyGraph, newVersion), version, newVersion),
 
-        // // add the composite delta to the L1-graph + highlight it as 'active':
+        // add the composite delta to the L1-graph + highlight it as 'active':
         dependencyGraphL1: addDeltaAndActivate(dependencyGraphL1, composite),
 
-        // // add the primitive L0-deltas to the L0-graph + highlight them as 'active':
+        // add the primitive L0-deltas to the L0-graph + highlight them as 'active':
         dependencyGraphL0: composite.deltas.reduce(
           (graph, delta) => {
             return addDeltaAndActivate(graph, delta);
@@ -409,13 +408,19 @@ export class App extends React.Component<{}, AppState> {
 
     const onCsEdit = (deltas: PrimitiveDelta[], description: string) => {
       const csComposite = this.state.cs.compositeLevel.createComposite(deltas, description);
-      const newCsVersion = this.state.cs.versionRegistry.createVersion(this.state.cs.version, csComposite);
 
-      const newCorrVersion = this.parser.parse(newCsVersion, this.state.corr.version);
-      const corrComposite = newCorrVersion.parents.find(([parentVersion, d]) => parentVersion === this.state.corr.version)?.[1] as CompositeDelta;
-      const newAsVersion = newCorrVersion.getEmbedded("as")?.embedded as Version;
-      const asComposite = newAsVersion!.parents.find(([parentVersion, d]) => parentVersion === this.state.as.version)?.[1] as CompositeDelta;
+      const {corr: corrComposite, as: asComposite, csOverrides, asOverrides} = this.parser.parse(csComposite,
+        this.state.cs.version, this.state.corr.version, this.state.as.version);
 
+      const newCsVersion = this.state.cs.versionRegistry.createVersion(this.state.cs.version, csComposite);
+      const newAsVersion = this.state.as.versionRegistry.createVersion(this.state.as.version, asComposite);
+      const newCorrVersion = this.state.corr.versionRegistry.createVersion(this.state.corr.version, corrComposite,
+        embed(
+          ["cs", newCsVersion, csOverrides],
+          ["as", newAsVersion, asOverrides],
+        ));
+
+      // This also trigggers a state update:
       this.state.cs.graphDeltaExecutor.exec(csComposite);
       this.state.corr.graphDeltaExecutor.exec(corrComposite);
       this.state.as.graphDeltaExecutor.exec(asComposite);
@@ -423,9 +428,9 @@ export class App extends React.Component<{}, AppState> {
       // Update state:
       this.setState(({cs, corr, as}) => {
         const result = {
-          cs: execChanges(cs, newCsVersion, csComposite),
-          corr: execChanges(corr, newCorrVersion, corrComposite),
-          as: execChanges(as, newAsVersion, asComposite),
+          cs: addVersionAndDeltas(cs, newCsVersion, csComposite),
+          corr: addVersionAndDeltas(corr, newCorrVersion, corrComposite),
+          as: addVersionAndDeltas(as, newAsVersion, asComposite),
         };
         return result;
       });
@@ -436,19 +441,14 @@ export class App extends React.Component<{}, AppState> {
         <Grid.Col span={1}>
           <VersionedModel title="Concrete Syntax"
             generateUUID={this.generateUUID}
-            // compositeLevel={this.state.cs.compositeLevel}
-            // versionRegistry={this.state.cs.versionRegistry}
             state={this.state.cs}
             setState={this.setCsState.bind(this)}
             setNextNodePosition={(x,y)=>{this.state.cs.manipulator.x = x; this.state.cs.manipulator.y = y;}}
-            // onNewVersion={handleNewCsVersion}
             onUserEdit={onCsEdit} />
         </Grid.Col>
         <Grid.Col span={1}>
           <VersionedModel title="Correspondence" readonly
             generateUUID={this.generateUUID}
-            // compositeLevel={this.state.corr.compositeLevel}
-            // versionRegistry={this.state.corr.versionRegistry}
             setNextNodePosition={(x,y)=>{this.state.corr.manipulator.x = x; this.state.corr.manipulator.y = y;}}
             state={this.state.corr}
             setState={this.setCorrState.bind(this)} />
@@ -466,8 +466,6 @@ export class App extends React.Component<{}, AppState> {
         <Grid.Col span={1}>
           <VersionedModel title="Abstract Syntax"
             generateUUID={this.generateUUID}
-            // compositeLevel={this.state.as.compositeLevel}
-            // versionRegistry={this.state.as.versionRegistry}
             setNextNodePosition={(x,y)=>{this.state.as.manipulator.x = x; this.state.as.manipulator.y = y;}}
             state={this.state.as}
             setState={this.setAsState.bind(this)}

+ 33 - 33
src/onion/version.test.ts

@@ -236,49 +236,49 @@ describe("Version", () => {
     });
   });
 
-  describe("Embedding of versions", () => {
-    it("Create embedded versions", () => {
-      const getId = mockUuid();
-      const registry = new VersionRegistry();
+  // describe("Embedding of versions", () => {
+  //   it("Create embedded versions", () => {
+  //     const getId = mockUuid();
+  //     const registry = new VersionRegistry();
 
-      const lvl1 = new CompositeLevel(); // transactions on CS or CORR
-      const lvl2 = new CompositeLevel(); // transactions on CS+CORR
+  //     const lvl1 = new CompositeLevel(); // transactions on CS or CORR
+  //     const lvl2 = new CompositeLevel(); // transactions on CS+CORR
 
-      // CS: a node is created, then deleted
-      const csNode = new NodeCreation(getId());
-      const csDel = new NodeDeletion(csNode, [], []);
+  //     // CS: a node is created, then deleted
+  //     const csNode = new NodeCreation(getId());
+  //     const csDel = new NodeDeletion(csNode, [], []);
 
-      const csNodeLvl1 = lvl1.createComposite([csNode]);
-      const csDelLvl1 = lvl1.createComposite([csDel]);
+  //     const csNodeLvl1 = lvl1.createComposite([csNode]);
+  //     const csDelLvl1 = lvl1.createComposite([csDel]);
 
-      const csV1 = registry.createVersion(initialVersion, csNodeLvl1);
-      const csV2 = registry.createVersion(csV1, csDelLvl1);
+  //     const csV1 = registry.createVersion(initialVersion, csNodeLvl1);
+  //     const csV2 = registry.createVersion(csV1, csDelLvl1);
 
-      // CORR: a link is created from a corr-node to the cs-node, followed by the deletion of the corr-node
-      // const corrLink = corrLvl.createComposite([csNode, new NodeCreation(getId()), corrLink]);
-      const corrNode = new NodeCreation(getId());
-      const corrLink = new EdgeCreation(corrNode, "cs", csNode);
-      const corrDel = new NodeDeletion(corrNode, [corrLink], []);
+  //     // CORR: a link is created from a corr-node to the cs-node, followed by the deletion of the corr-node
+  //     // const corrLink = corrLvl.createComposite([csNode, new NodeCreation(getId()), corrLink]);
+  //     const corrNode = new NodeCreation(getId());
+  //     const corrLink = new EdgeCreation(corrNode, "cs", csNode);
+  //     const corrDel = new NodeDeletion(corrNode, [corrLink], []);
 
-      const corrLinkLvl1 = lvl1.createComposite([corrNode, corrLink]);
+  //     const corrLinkLvl1 = lvl1.createComposite([corrNode, corrLink]);
 
-      const csCorrLinkLvl2 = lvl2.createComposite([csNodeLvl1, corrLinkLvl1]);
+  //     const csCorrLinkLvl2 = lvl2.createComposite([csNodeLvl1, corrLinkLvl1]);
 
-      // Patch: in order to avoid a conflict between csDel and corrLink, in corrV2, we override csDel by csDel1.
-      const csDel1 = new NodeDeletion(csNode, [], [corrDel]);
+  //     // Patch: in order to avoid a conflict between csDel and corrLink, in corrV2, we override csDel by csDel1.
+  //     const csDel1 = new NodeDeletion(csNode, [], [corrDel]);
 
-      const corrDelLvl1 = lvl1.createComposite([corrDel]);
-      const csDel1Lvl1 = lvl1.createComposite([csDel1]);
+  //     const corrDelLvl1 = lvl1.createComposite([corrDel]);
+  //     const csDel1Lvl1 = lvl1.createComposite([csDel1]);
 
-      const csCorrDelLvl2 = lvl2.createComposite([corrDelLvl1, csDel1Lvl1]);
+  //     const csCorrDelLvl2 = lvl2.createComposite([corrDelLvl1, csDel1Lvl1]);
 
-      const corrV1 = registry.createVersion(initialVersion, csCorrLinkLvl2, embed(["cs", csV1, overrideDeltas()]));
-      const corrV2 = registry.createVersion(corrV1, csCorrDelLvl2, embed(["cs", csV2, overrideDeltas( [csDel, csDel1] )]));
+  //     const corrV1 = registry.createVersion(initialVersion, csCorrLinkLvl2, embed(["cs", csV1, overrideDeltas()]));
+  //     const corrV2 = registry.createVersion(corrV1, csCorrDelLvl2, embed(["cs", csV2, overrideDeltas( [csDel, csDel1] )]));
 
-      assertThrows(() => {
-        // same as above, but without the overridden deltas:
-        registry.createVersion(corrV1, csCorrDelLvl2, embed(["cs", csV2, overrideDeltas()])); // should throw
-      }, "should not be able to create a version that embeds csV2 without overriding csDel");
-    });
-  });
+  //     assertThrows(() => {
+  //       // same as above, but without the overridden deltas:
+  //       registry.createVersion(corrV1, csCorrDelLvl2, embed(["cs", csV2, overrideDeltas()])); // should throw
+  //     }, "should not be able to create a version that embeds csV2 without overriding csDel");
+  //   });
+  // });
 });

+ 64 - 58
src/onion/version.ts

@@ -25,12 +25,15 @@ export interface EmbeddedVersion {
 
 export type EmbeddedVersionMap = Map<string, EmbeddedVersion>;
 
-// Just a shorthand
+export type ParentLink = [Version, Delta, EmbeddedVersionMap];
+
+
+// Just a shorthand and more readable
 export function embed(...triples: [string, Version, Map<Delta,Delta>][]): EmbeddedVersionMap {
   return new Map(triples.map(([id, version, overriddenDeltas]) => [id, {embedded: version, overriddenDeltas}]));
 }
 
-// Just a shorthand
+// Just a shorthand and more readable
 export function overrideDeltas(...tuples: [Delta,Delta][]): Map<Delta,Delta> {
   return new Map(tuples);
 }
@@ -43,9 +46,9 @@ type VersionLink = [('p'|'c'), Delta];
 
 // not exported -> use VersionRegistry to create versions
 export class Version {
-  readonly parents: Array<[Version, Delta]>;
+  readonly parents: Array<ParentLink>;
   readonly children: Array<[Version, Delta]> = [];
-  private readonly embeddings: EmbeddedVersionMap; // do not access directly - use getEmbedded() instead
+  // private readonly embeddings: EmbeddedVersionMap; // do not access directly - use getEmbedded() instead
 
   // Unique ID of the version - XOR of all delta hashes - guarantees that Versions with equal (unordered) sets of Deltas have the same ID.
   readonly hash: Buffer;
@@ -53,12 +56,12 @@ export class Version {
   readonly size: number;
 
   // DO NOT USE constructor directly - instead use VersionRegistry.createVersion.
-  constructor(parents: Array<[Version, Delta]>, embeddings: EmbeddedVersionMap, hash: Buffer, size: number) {
+  constructor(parents: Array<ParentLink>, hash: Buffer, size: number) {
     this.parents = parents;
     for (const [parent,delta] of parents) {
       parent.children.push([this, delta]);
     }
-    this.embeddings = embeddings;
+    // this.embeddings = embeddings;
     this.hash = hash;
     this.size = size;
   }
@@ -68,6 +71,7 @@ export class Version {
   *[Symbol.iterator](): Iterator<Delta> {
     let current: Version = this;
     while (current.parents.length !== 0) {
+      // There may be multiple parents due to commutativity (multiple orders of deltas that yield the same version), but it doesn't matter which one we pick: all paths will yield the same set of deltas.
       const [parent, delta] = current.parents[0];
       yield delta;
       current = parent;
@@ -75,7 +79,8 @@ export class Version {
   }
 
   *iterPrimitiveDeltas(): Iterable<Delta> {
-    for (const d of this) {
+    const executionOrder = [...this].reverse();
+    for (const d of executionOrder) {
       yield* d.iterPrimitiveDeltas();
     }
   }
@@ -114,16 +119,17 @@ export class Version {
     return findDFS<Version,VersionLink>(this, otherVersion, getNeighbors);
   }
 
-  getEmbedded(id: string): EmbeddedVersion | undefined {
-    return this.embeddings.get(id);
-  }
-
-  getEmbeddedOrThrow(id: string): EmbeddedVersion {
-    const result = this.embeddings.get(id);
-    if (result === undefined) {
-      throw new Error("No such embedding: " + id);
+  // Is the given Delta overridden in this version? Then this function returns the Delta that replaces it.
+  findOverride<T extends Delta>(embedKey: string, delta: T): T | undefined {
+    if (this.parents.length > 0) {
+      const [parentVersion, _, map] = this.parents[0];
+      const embedding = map.get(embedKey);
+      if (embedding === undefined) {
+        throw new Error("Wrong embedKey: " + embedKey);
+      }
+      const found = embedding.overriddenDeltas.get(delta) as T;
+      return found || parentVersion.findOverride<T>(embedKey, delta);
     }
-    return result;
   }
 }
 
@@ -131,25 +137,25 @@ const initialHash = Buffer.alloc(32); // all zeros
 
 export class InitialVersion extends Version {
   private static instance: InitialVersion = new InitialVersion(); // singleton pattern
-  private static embedded: EmbeddedVersion = {embedded: InitialVersion.instance, overriddenDeltas: new Map()};
+  // private static embedded: EmbeddedVersion = {embedded: InitialVersion.instance, overriddenDeltas: new Map()};
 
   private constructor() {
-    super([], new Map(), initialHash, 0);
+    super([], initialHash, 0);
   }
 
   static getInstance(): InitialVersion {
     return InitialVersion.instance;
   }
 
-  // override: we pretend that the initial version has all other empty models (also represented by the initial version) embedded into it.
-  getEmbedded(id: string): EmbeddedVersion| undefined {
-    return InitialVersion.embedded;
-  }
+  // // override: we pretend that the initial version has all other empty models (also represented by the initial version) embedded into it.
+  // getEmbedded(id: string): EmbeddedVersion| undefined {
+  //   return InitialVersion.embedded;
+  // }
 
-  // override: we pretend that the initial version has all other empty models (also represented by the initial version) embedded into it.
-  getEmbeddedOrThrow(id: string): EmbeddedVersion {
-    return InitialVersion.embedded;
-  }
+  // // override: we pretend that the initial version has all other empty models (also represented by the initial version) embedded into it.
+  // getEmbeddedOrThrow(id: string): EmbeddedVersion {
+  //   return InitialVersion.embedded;
+  // }
 }
 
 // The initial, empty version.
@@ -198,36 +204,36 @@ export class VersionRegistry {
       throw new Error("Delta " + delta.getDescription() + " conflicts with " + conflictsWith.getDescription());
     }
 
-    const primitives = [...delta.iterPrimitiveDeltas()];
-
-    for (const [guestId, {embedded, overriddenDeltas}] of embeddings.entries()) {
-      const parentEmbedding = parent.getEmbedded(guestId);
-      // Check pre-condition 3:
-      if (parentEmbedding === undefined) {
-        throw new Error("Attempted to create illegal version: parent version does not embed '" + guestId + "'");
-      }
-      const {embedded: parentEmbedded} = parentEmbedding;
-      // Check pre-condition 4:
-      const found = embedded.parents.find(([parentOfEmbedded]) => (parentOfEmbedded === parentEmbedded));
-      if (found === undefined) {
-        throw new Error("Attempted to create illegal version that embeds a guest, whose parent is not embedded by the parent of the to-be-created version.");
-      }
-      else {
-        // Check pre-condition 5:
-        // console.log("primitives:", primitives);
-        const [parentOfEmbedded, embeddedDelta] = found;
-        for (const embeddedPrimitive of embeddedDelta.iterPrimitiveDeltas()) {
-          // console.log("embeddedPrimitive:", embeddedPrimitive);
-          const haveOriginal = primitives.includes(embeddedPrimitive);
-          const overridden = overriddenDeltas.get(embeddedPrimitive);
-          const haveOverridden = ((overridden !== undefined) && primitives.includes(overridden));
-          // console.log(haveOriginal, haveOverridden)
-          if (!haveOriginal && !haveOverridden || (haveOriginal && haveOverridden)) { // logical XOR
-            throw new Error("Attempt to create illegal version: must contain all deltas (some possibly overridden) of all embedded versions.");
-          }
-        }
-      }
-    }
+    // const primitives = [...delta.iterPrimitiveDeltas()];
+
+    // for (const [guestId, {embedded, overriddenDeltas}] of embeddings.entries()) {
+    //   const parentEmbedding = parent.getEmbedded(guestId);
+    //   // Check pre-condition 3:
+    //   if (parentEmbedding === undefined) {
+    //     throw new Error("Attempted to create illegal version: parent version does not embed '" + guestId + "'");
+    //   }
+    //   const {embedded: parentEmbedded} = parentEmbedding;
+    //   // Check pre-condition 4:
+    //   const found = embedded.parents.find(([parentOfEmbedded]) => (parentOfEmbedded === parentEmbedded));
+    //   if (found === undefined) {
+    //     throw new Error("Attempted to create illegal version that embeds a guest, whose parent is not embedded by the parent of the to-be-created version.");
+    //   }
+    //   else {
+    //     // Check pre-condition 5:
+    //     // console.log("primitives:", primitives);
+    //     const [parentOfEmbedded, embeddedDelta] = found;
+    //     for (const embeddedPrimitive of embeddedDelta.iterPrimitiveDeltas()) {
+    //       // console.log("embeddedPrimitive:", embeddedPrimitive);
+    //       const haveOriginal = primitives.includes(embeddedPrimitive);
+    //       const overridden = overriddenDeltas.get(embeddedPrimitive);
+    //       const haveOverridden = ((overridden !== undefined) && primitives.includes(overridden));
+    //       // console.log(haveOriginal, haveOverridden)
+    //       if (!haveOriginal && !haveOverridden || (haveOriginal && haveOverridden)) { // logical XOR
+    //         throw new Error("Attempt to create illegal version: must contain all deltas (some possibly overridden) of all embedded versions.");
+    //       }
+    //     }
+    //   }
+    // }
     return this.createVersionUnsafe(parent, delta, embeddings);
   }
 
@@ -241,12 +247,12 @@ export class VersionRegistry {
       const havePath = existingVersion.parents.some(([parentVersion, delta]) => parentVersion === parent);
       if (!havePath) {
         // but the path is new (there can be multiple 'paths' to the same version, because of commutation of deltas)
-        existingVersion.parents.push([parent, delta]);
+        existingVersion.parents.push([parent, delta, embeddings]);
         parent.children.push([existingVersion, delta]);
       }
       return existingVersion;
     } else {
-      const version = new Version([[parent, delta]], embeddings, newHash, parent.size + 1);
+      const version = new Version([[parent, delta, embeddings]], newHash, parent.size + 1);
       this.putVersion(newHash, version);
       return version;
     }

+ 10 - 4
src/parser/parser.ts

@@ -1,8 +1,14 @@
 import {Version} from "../onion/version";
+import {Delta} from "../onion/delta";
+import {CompositeDelta} from "../onion/composite_delta";
+
+export interface ParseResult {
+  corr: CompositeDelta;
+  as: CompositeDelta;
+  csOverrides: Map<Delta,Delta>;
+  asOverrides: Map<Delta,Delta>;
+}
 
 export interface Parser {
-  // cs: the new (modified) CS version
-  // parentCorr: the latest CORR version, that embeds the parent of 'cs'.
-  // Returns a new CORR version that embeds 'cs'.
-  parse(cs: Version, parentCorr: Version): Version;
+  parse(csDelta: CompositeDelta, csParent: Version, corrParent: Version, asParent): ParseResult;
 }

+ 63 - 15
src/parser/trivial_parser.test.ts

@@ -18,32 +18,80 @@ import {assert} from "../util/assert";
 
 describe("Trivial Parser", () => {
   it("Parse CS creation and deletion", () => {
-    const registry = new VersionRegistry();
+    const csRegistry = new VersionRegistry();
+    const corrRegistry = new VersionRegistry();
+    const asRegistry = new VersionRegistry();
     const csLvl = new CompositeLevel(); // L1
     const asLvl = new CompositeLevel(); // L1
     const corrLvl = new CompositeLevel(); // L1
     const getUuid = mockUuid();
-    const parser = new TrivialParser(getUuid, registry, {csLvl, asLvl, corrLvl});
+    const parser = new TrivialParser(getUuid, {csLvl, asLvl, corrLvl});
 
-    const csInitial = initialVersion;
-    const corrInitial = initialVersion;
-    const asInitital = initialVersion;
+    const csV0 = initialVersion;
+    const corrV0 = initialVersion;
+    const asV0 = initialVersion;
 
     const csCreation = new NodeCreation(getUuid());
     const csDeletion = new NodeDeletion(csCreation, [], []);
 
-    const csV1 = registry.createVersion(csInitial, csLvl.createComposite([csCreation]));
-    const csV2 = registry.createVersion(csV1, csLvl.createComposite([csDeletion]));
+    const csV1Composite = csLvl.createComposite([csCreation]);
+    const csV2Composite = csLvl.createComposite([csDeletion]);
 
-    const corrV1 = parser.parse(csV1, corrInitial);
-    const corrV2 = parser.parse(csV2, corrV1);
+    const {corr: corrV1Composite, as: asV1Composite} = parser.parse(csV1Composite, csV0, corrV0, asV0);
+
+    const csV1 = csRegistry.createVersion(csV0, csV1Composite);
+    const corrV1 = corrRegistry.createVersion(corrV0, corrV1Composite);
+    const asV1 = asRegistry.createVersion(asV0, asV1Composite);
+
+    const {corr: corrV2Composite, as: asV2Composite} = parser.parse(csV2Composite, csV1, corrV1, asV1);
+
+    const csV2 = csRegistry.createVersion(csV1, csV2Composite);
+    const corrV2 = corrRegistry.createVersion(corrV1, corrV2Composite);
+    const asV2 = asRegistry.createVersion(asV1, asV2Composite);
 
-    const asV2 = corrV2.getEmbedded("as")?.embedded;
-    if (asV2 === undefined) {
-      throw new Error("Expected asV2 to exist!");
-    }
-    
     const asV2Primitives = [...asV2.iterPrimitiveDeltas()];
     assert(asV2Primitives.length === 2, "Expected 2 primitive deltas on AS");
   });
-});
+
+  it("Parse CS creation and deletion (with CS edge)", () => {
+    const csRegistry = new VersionRegistry();
+    const corrRegistry = new VersionRegistry();
+    const asRegistry = new VersionRegistry();
+    const csLvl = new CompositeLevel(); // L1
+    const asLvl = new CompositeLevel(); // L1
+    const corrLvl = new CompositeLevel(); // L1
+    const getUuid = mockUuid();
+    const parser = new TrivialParser(getUuid, {csLvl, asLvl, corrLvl});
+
+    // Same as before, but this time, we also create an edge in CS:
+
+    const csV0 = initialVersion;
+    const corrV0 = initialVersion;
+    const asV0 = initialVersion;
+
+    const csCreation = new NodeCreation(getUuid());
+    const csCreateEdge = new EdgeCreation(csCreation, "x", csCreation); // self-edge
+    const csUnsetEdge = new EdgeUpdate(csCreateEdge, null);
+    const csDeletion = new NodeDeletion(csCreation, [csUnsetEdge], [csUnsetEdge]);
+    assert(csDeletion.getConflicts().length === 0, "expected CS deletion to not have conflicts (yet).");
+
+    const csTransactions = [
+      csLvl.createComposite([csCreation]),
+      csLvl.createComposite([csCreateEdge]),
+      csLvl.createComposite([csUnsetEdge]),
+      csLvl.createComposite([csDeletion]),
+    ];
+
+    // Parse all CS composites:
+    const [csFinal, corrFinal, asFial] = csTransactions.reduce(([csParent, corrParent, asParent], csComposite) => {
+      const {corr: corrComposite, as: asComposite} = parser.parse(csComposite, csParent, corrParent, asParent);
+      return [
+        csRegistry.createVersion(csParent, csComposite),
+        corrRegistry.createVersion(corrParent, corrComposite),
+        asRegistry.createVersion(asParent, asComposite),
+      ];
+    }, [csV0, corrV0, asV0]);
+
+    console.log("corrFinal primitives:", [...corrFinal.iterPrimitiveDeltas()])
+  });
+});

+ 19 - 48
src/parser/trivial_parser.ts

@@ -1,4 +1,4 @@
-import {Parser} from "./parser";
+import {Parser, ParseResult} from "./parser";
 
 import {Delta} from "../onion/delta";
 import {UUID} from "../onion/types";
@@ -23,54 +23,27 @@ import {
 } from "../onion/composite_delta";
 
 
-function getParentLink(cs: Version, parentCorr: Version): [Version,Delta] {
-  const parentCsEmbedding = parentCorr.getEmbedded("cs");
-  if (parentCsEmbedding === undefined) {
-    throw new Error("Parent correspondence model has no 'cs' embedding!");
-  }
-  const csParentLink = cs.parents.find(([parentCs]) =>  parentCsEmbedding.embedded === parentCs);
-  if (csParentLink === undefined) {
-    throw new Error("Parent correspondence model does not embed parent of 'cs' model!");
-  }
-  return csParentLink;
-}
-
 // A parser that creates an AS-node for every CS-node, with a Corr-node in between.
 export class TrivialParser implements Parser {
   readonly getUuid: () => UUID;
-  readonly registry: VersionRegistry;
 
   readonly csLvl: CompositeLevel;
   readonly asLvl: CompositeLevel;
   readonly corrLvl: CompositeLevel;
 
-  constructor(getUuid: () => UUID, registry: VersionRegistry,
+  constructor(getUuid: () => UUID,
     compositeLvls: {csLvl: CompositeLevel, asLvl: CompositeLevel, corrLvl: CompositeLevel}) {
     this.getUuid = getUuid;
-    this.registry = registry;
 
     this.csLvl = compositeLvls.csLvl;
     this.asLvl = compositeLvls.asLvl;
     this.corrLvl = compositeLvls.corrLvl;
   }
 
-  parse(cs: Version, parentCorr: Version): Version {
-    const override = (map) => (d => map.get(d) || d);
-
-    const [csParent, csComposite] = getParentLink(cs, parentCorr);
-    const parentAs = parentCorr.getEmbedded("as");
-
-    if (!(csComposite instanceof CompositeDelta)) {
-      throw new Error("Assertion failed: csComposite must be CompositeDelta")
-    }
-
-    if (parentAs === undefined) {
-      throw new Error("Assertion failed: parentCorr must embed 'as'");
-    }
-
+  parse(csComposite: CompositeDelta, csParent: Version, corrParent: Version, asParent): ParseResult {
     // these are the new deltas in 'cs'
     const csDeltas = [...csComposite.iterPrimitiveDeltas()];
-    const parentCorrDeltas = new Set(parentCorr.iterPrimitiveDeltas());
+    const parentCorrDeltas = new Set(corrParent.iterPrimitiveDeltas());
 
     const asDeltas: Delta[] = [];
     const corrDeltas: Delta[] = [];
@@ -89,6 +62,8 @@ export class TrivialParser implements Parser {
         corrDeltas.push(corrCreation, corr2Cs, corr2As);
       }
       else if (csDelta instanceof NodeDeletion) {
+        console.log("handling CS deletion...");
+
         const csCreation = csDelta.creation; // the NodeCreation of the deleted cs node
 
         // csDelta will conflict with our earlier 'corr2Cs' EdgeCreation delta:
@@ -115,7 +90,9 @@ export class TrivialParser implements Parser {
         const asDeletion1 = new NodeDeletion(asCreation, [], [corrDeletion]);
 
         // We already have the deletion in the CS model, so we only need to create another one to be used in the CORR model:
-        const csDeletion1 = new NodeDeletion(csCreation, csDelta.deletedOutgoingEdges, csDelta.afterIncomingEdges.concat(corrDeletion));
+        const csDeletion1 = new NodeDeletion(csCreation,
+          csDelta.deletedOutgoingEdges.map(d => corrParent.findOverride("cs", d) || d),
+          csDelta.afterIncomingEdges.map(d => corrParent.findOverride("cs", d) || d).concat(corrDeletion));
         // const csDeletion1 = new NodeDeletion(csCreation, [], [corrDeletion]);
 
         csOverrides.set(csDelta, csDeletion1);
@@ -126,23 +103,12 @@ export class TrivialParser implements Parser {
       }
     }
 
-    const as = (() => {
-      // Check this later: Maybe not create a new AS version if asDeltas.length === 0 ?
-
-      // if (asDeltas.length > 0) {
-        // Create new AS-version:
-        const asComposite = this.asLvl.createComposite(asDeltas, "parse:"+csComposite.getDescription());
-        return this.registry.createVersion(parentAs.embedded, asComposite);
-      // } else {
-      //   // no new AS version:
-      //   return parentAs.embedded;
-      // }
-    })();
-
+    const asComposite = this.asLvl.createComposite(asDeltas, "parse:"+csComposite.getDescription());
 
     // New CORR-version:
-    const csDeltas1 = csDeltas.map(override(csOverrides));
-    const asDeltas1 = asDeltas.map(override(asOverrides));
+    const csDeltas1 = csDeltas.map(d => csOverrides.get(d) || d);
+    const asDeltas1 = asDeltas.map(d => asOverrides.get(d) || d);
+
     // the order in which corr-deltas are put in corrComposite matters - deltas must occur after their dependencies:
     const orderedByDependency: Delta[] = [];
     visitPartialOrdering(
@@ -150,6 +116,11 @@ export class TrivialParser implements Parser {
       (d: Delta) => d.getDependencies(),
       (d: Delta) => orderedByDependency.push(d));
     const corrComposite = this.corrLvl.createComposite(orderedByDependency, "corr-parse:"+csComposite.getDescription());
-    return this.registry.createVersion(parentCorr, corrComposite, embed(["cs", cs, csOverrides], ["as", as, asOverrides]));
+    return {
+      corr: corrComposite,
+      as: asComposite,
+      csOverrides,
+      asOverrides,
+    };
   }
 }