Procházet zdrojové kódy

Added (trivial) renderer to frontend

Joeri Exelmans před 2 roky
rodič
revize
4e99665a23

+ 54 - 42
src/frontend/app.tsx

@@ -330,7 +330,8 @@ export class App extends React.Component<{}, AppState> {
   // versionRegistry = new VersionRegistry();
   parser: TrivialParser;
 
-  renderedBy: Map<Version,{corr: Version, as: Version}>;
+  parsedTo: Map<Version,{corr: Version, target: Version}>;
+  renderedTo: Map<Version,{corr: Version, target: Version}>;
 
   setCsState(callback) {
     return this.setState(({cs, ...rest}) => ({cs: callback(cs), ...rest}));
@@ -374,7 +375,8 @@ export class App extends React.Component<{}, AppState> {
       as: makeModelState(this.setAsState.bind(this)),
     };
 
-    this.renderedBy = new Map([[this.state.cs.versionRegistry.initialVersion, {corr:this.state.corr.versionRegistry.initialVersion, as:this.state.as.versionRegistry.initialVersion}]]);
+    this.parsedTo = new Map([[this.state.cs.versionRegistry.initialVersion, {corr:this.state.corr.versionRegistry.initialVersion, target:this.state.as.versionRegistry.initialVersion}]]);
+    this.renderedTo = new Map([[this.state.as.versionRegistry.initialVersion, {corr:this.state.corr.versionRegistry.initialVersion, target:this.state.cs.versionRegistry.initialVersion}]]);
 
     this.parser = new TrivialParser(this.generateUUID,
       {
@@ -463,47 +465,57 @@ export class App extends React.Component<{}, AppState> {
       }));
     }
 
-
-    const onCsEdit = (deltas: PrimitiveDelta[], description: string) => {
-      const csComposite = this.state.cs.compositeLevel.createComposite(deltas, description);
-
-      const csParent = this.state.cs.version;
-      const {corr: corrParent, as: asParent} = this.renderedBy.get(csParent) || (()=>{throw new Error("X")})();
-
-      const {corr: corrComposite, as: asComposite, csOverrides, asOverrides} = this.parser.parse(
-        csComposite,
-        csParent, corrParent, asParent,
-      );
-
-      const newCsVersion = this.state.cs.versionRegistry.createVersion(this.state.cs.version, csComposite);
-
-      const newAsVersion =  (asComposite.deltas.length > 0) ?
-        this.state.as.versionRegistry.createVersion(asParent, asComposite)
-        : this.state.as.version; // do not create a new AS version
-
-      const newCorrVersion = this.state.corr.versionRegistry.createVersion(corrParent, corrComposite,
-        embed(
-          ["cs", newCsVersion, csOverrides],
-          ["as", newAsVersion, asOverrides],
-        ));
-
-      this.renderedBy.set(newCsVersion, {corr: newCorrVersion, as: newAsVersion});
-
-      // Update state:
-      this.setState(({cs, corr, as}) => {
-        const result = {
-          cs: addVersionAndDeltas(cs, newCsVersion, csComposite),
-          corr: addVersionAndDeltas(corr, newCorrVersion, corrComposite),
-          as: addVersionAndDeltas(as, newAsVersion, asComposite),
-        };
-        return result;
-      });
-
-      gotoVersion(this.state.cs, this.setCsState.bind(this), newCsVersion);
-      gotoVersion(this.state.corr, this.setCorrState.bind(this), newCorrVersion);
-      gotoVersion(this.state.as, this.setAsState.bind(this), newAsVersion);
+    const onEdit = (sourceState: VersionedModelState, targetState: VersionedModelState,
+          mapping, parse: boolean) => {
+      return (deltas: PrimitiveDelta[], description: string) => {
+        const sourceComposite = sourceState.compositeLevel.createComposite(deltas, description);
+
+        const sourceParent = sourceState.version;
+        const {corr: corrParent, target: targetParent} = mapping.get(sourceParent) || (()=>{throw new Error("X")})();
+
+        const {corrComposite, targetComposite, sourceOverrides, targetOverrides} = this.parser.propagate_change(sourceComposite, sourceParent, corrParent, targetParent, targetState.compositeLevel, parse ? "parsed" : "rendered");
+
+        const newSourceVersion = sourceState.versionRegistry.createVersion(sourceState.version, sourceComposite);
+
+        const newTargetVersion =  (targetComposite.deltas.length > 0) ?
+          targetState.versionRegistry.createVersion(targetParent, targetComposite)
+          : targetState.version; // do not create a new target version
+
+        const csComposite = parse ? sourceComposite : targetComposite;
+        const asComposite = parse ? targetComposite : sourceComposite;
+        const newCsVersion = parse ? newSourceVersion : newTargetVersion;
+        const newAsVersion = parse ? newTargetVersion : newSourceVersion;
+        const csOverrides = parse ? sourceOverrides : targetOverrides;
+        const asOverrides = parse ? targetOverrides : sourceOverrides;
+
+        const newCorrVersion = this.state.corr.versionRegistry.createVersion(corrParent, corrComposite,
+          embed(
+            ["cs", newCsVersion, csOverrides],
+            ["as", newAsVersion, asOverrides],
+          ));
+
+        this.parsedTo.set(newCsVersion, {corr: newCorrVersion, target: newAsVersion});
+        this.renderedTo.set(newAsVersion, {corr: newCorrVersion, target: newCsVersion});
+
+        // Update state:
+        this.setState(({cs, corr, as}) => {
+          const result = {
+            cs: addVersionAndDeltas(cs, newCsVersion, csComposite),
+            corr: addVersionAndDeltas(corr, newCorrVersion, corrComposite),
+            as: addVersionAndDeltas(as, newAsVersion, asComposite),
+          };
+          return result;
+        });
+
+        gotoVersion(this.state.cs, this.setCsState.bind(this), newCsVersion);
+        gotoVersion(this.state.corr, this.setCorrState.bind(this), newCorrVersion);
+        gotoVersion(this.state.as, this.setAsState.bind(this), newAsVersion);
+      };
     };
 
+    const onCsEdit = onEdit(this.state.cs, this.state.as, this.parsedTo, true);
+    const onAsEdit = onEdit(this.state.as, this.state.cs, this.renderedTo, false);
+
     return (<>
       <Grid grow>
         <Grid.Col span={1}>
@@ -536,6 +548,7 @@ export class App extends React.Component<{}, AppState> {
             setNextNodePosition={(x,y)=>{this.state.as.manipulator.x = x; this.state.as.manipulator.y = y;}}
             state={this.state.as}
             setState={this.setAsState.bind(this)}
+            onUserEdit={onAsEdit}
             onUndoClicked={onUndoClicked.bind(null, this.state.as, this.setAsState.bind(this))}
             onRedoClicked={onRedoClicked.bind(null, this.state.as, this.setAsState.bind(this))}
             onVersionClicked={version => gotoVersion(this.state.as, this.setAsState.bind(this), version)}
@@ -545,4 +558,3 @@ export class App extends React.Component<{}, AppState> {
     </>);
   }
 }
-

+ 8 - 4
src/parser/parser.ts

@@ -2,13 +2,17 @@ import {Version} from "../onion/version";
 import {Delta} from "../onion/delta";
 import {CompositeDelta} from "../onion/composite_delta";
 
-export interface ParseResult {
-  corr: CompositeDelta;
-  as: CompositeDelta;
+export interface ParseOrRenderResult {
+  corrComposite: CompositeDelta;
+  targetComposite: CompositeDelta;
   csOverrides: Map<Delta,Delta>;
   asOverrides: Map<Delta,Delta>;
 }
 
 export interface Parser {
-  parse(csDelta: CompositeDelta, csParent: Version, corrParent: Version, asParent): ParseResult;
+  parse(csDelta: CompositeDelta, csParent: Version, corrParent: Version, asParent: Version): ParseOrRenderResult;
+}
+
+export interface Renderer {
+  render(asDelta: CompositeDelta, csParent: Version, corrParent: Version, asParent: Version): ParseOrRenderResult;
 }

+ 3 - 3
src/parser/trivial_parser.test.ts

@@ -37,13 +37,13 @@ describe("Trivial Parser", () => {
     const corrV0 = corrRegistry.initialVersion;
     const asV0 = asRegistry.initialVersion;
     
-    const {corr: corrV1Composite, as: asV1Composite} = parser.parse(csV1Composite, csV0, corrV0, asV0);
+    const {corrComposite: corrV1Composite, targetComposite: 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 {corrComposite: corrV2Composite, targetComposite: asV2Composite} = parser.parse(csV2Composite, csV1, corrV1, asV1);
 
     const csV2 = csRegistry.createVersion(csV1, csV2Composite);
     const corrV2 = corrRegistry.createVersion(corrV1, corrV2Composite);
@@ -84,7 +84,7 @@ describe("Trivial Parser", () => {
 
     // 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);
+      const {corrComposite, targetComposite: asComposite} = parser.parse(csComposite, csParent, corrParent, asParent);
       return [
         csRegistry.createVersion(csParent, csComposite),
         corrRegistry.createVersion(corrParent, corrComposite),

+ 72 - 45
src/parser/trivial_parser.ts

@@ -1,4 +1,8 @@
-import {Parser, ParseResult} from "./parser";
+import {
+  Parser,
+  Renderer,
+  ParseOrRenderResult,
+} from "./parser";
 
 import {Delta} from "../onion/delta";
 import {UUID} from "../onion/types";
@@ -24,7 +28,7 @@ import {
 
 
 // A parser that creates an AS-node for every CS-node, with a Corr-node in between.
-export class TrivialParser implements Parser {
+export class TrivialParser implements Parser, Renderer {
   readonly getUuid: () => UUID;
 
   readonly csLvl: CompositeLevel;
@@ -40,85 +44,108 @@ export class TrivialParser implements Parser {
     this.corrLvl = compositeLvls.corrLvl;
   }
 
-  parse(csComposite: CompositeDelta, csParent: Version, corrParent: Version, asParent): ParseResult {
-    // these are the new deltas in 'cs'
-    const csDeltas = [...csComposite.iterPrimitiveDeltas()];
+  // We can use pretty much the same code for both parsing and rendering :)
+  propagate_change(sourceComposite: CompositeDelta, sourceParent: Version, corrParent: Version, targetParent: Version, targetLvl: CompositeLevel, compositeDescriptionPrefix: string) {
+    const sourceDeltas = [...sourceComposite.iterPrimitiveDeltas()];
     const parentCorrDeltas = new Set(corrParent.iterPrimitiveDeltas());
 
-    const asDeltas: Delta[] = [];
+    const targetDeltas: Delta[] = [];
     const corrDeltas: Delta[] = [];
 
-    const csOverrides = new Map();
-    const asOverrides = new Map();
+    const sourceOverrides = new Map();
+    const targetOverrides = new Map();
 
-    for (const csDelta of csDeltas) {
-      if (csDelta instanceof NodeCreation) {
+    for (const sourceDelta of sourceDeltas) {
+      if (sourceDelta instanceof NodeCreation) {
+        const sourceCreation = sourceDelta;
         const corrCreation = new NodeCreation(this.getUuid());
-        const corr2Cs = new EdgeCreation(corrCreation, "cs", csDelta);
-        const asCreation = new NodeCreation(this.getUuid());
-        const corr2As = new EdgeCreation(corrCreation, "as", asCreation);
+        const corr2Source = new EdgeCreation(corrCreation, "cs", sourceCreation);
+        const targetCreation = new NodeCreation(this.getUuid());
+        const corr2Target = new EdgeCreation(corrCreation, "as", targetCreation);
 
-        asDeltas.push(asCreation);
-        corrDeltas.push(corrCreation, corr2Cs, corr2As);
+        targetDeltas.push(targetCreation);
+        corrDeltas.push(corrCreation, corr2Source, corr2Target);
       }
-      else if (csDelta instanceof NodeDeletion) {
-        const csCreation = csDelta.creation; // the NodeCreation of the deleted cs node
+      else if (sourceDelta instanceof NodeDeletion) {
+        const sourceDeletion = sourceDelta; // alias for readability :)
+        const csCreation = sourceDeletion.creation; // the NodeCreation of the deleted cs node
 
-        // csDelta will conflict with our earlier 'corr2Cs' EdgeCreation delta:
-        const corr2Cs = csDelta.edgeTargetConflicts.find(e => parentCorrDeltas.has(e));
-        if (corr2Cs === undefined) {
+        // sourceDeletion will conflict with our earlier 'corr2Source' EdgeCreation delta:
+        const corr2Source = sourceDeletion.edgeTargetConflicts.find(e => parentCorrDeltas.has(e));
+        if (corr2Source === undefined) {
           throw new Error("Assertion failed: When a node is deleted, the deletion must be conflicting with the creation of an incoming correspondence edge.");
         }
-        const corrCreation = corr2Cs.getCreation().source;
+        const corrCreation = corr2Source.getCreation().source;
         // corrCreation will have only one other outgoing edge:
-        const corr2As = corrCreation.outgoingEdges.find(e => e !== corr2Cs);
-        if (corr2As === undefined) {
+        const corr2Target = corrCreation.outgoingEdges.find(e => e !== corr2Source);
+        if (corr2Target === undefined) {
           throw new Error("Assertion failed: The correspondence node must have two outgoing edges, one to CS and one to AS.");
         }
-        const asCreation = corr2As.target.getTarget();
-        if (! (asCreation instanceof NodeCreation)) {
-          throw new Error("Assertion failed: The target of corr2As must be the NodeCreation of the AS node.");
+        const targetCreation = corr2Target.target.getTarget();
+        if (! (targetCreation instanceof NodeCreation)) {
+          throw new Error("Assertion failed: The target of corr2Target must be the NodeCreation of the AS node.");
         }
 
-        const corrDeletion = new NodeDeletion(corrCreation, [corr2Cs, corr2As], []);
+        const corrDeletion = new NodeDeletion(corrCreation, [corr2Source, corr2Target], []);
 
         // We create 2 (conflicting) asDeletions:
         // one that is to be used only in the AS model, unaware of any CORR-stuff, and one that is to be used in the CORR model.
-        const asDeletion = new NodeDeletion(asCreation, [], []); // only part of AS
-        const asDeletion1 = new NodeDeletion(asCreation, [], [corrDeletion]); // only part of CORR (override)
+        const targetDeletion = new NodeDeletion(targetCreation, [], []); // only part of AS
+        const targetDeletion1 = new NodeDeletion(targetCreation, [], [corrDeletion]); // only part of CORR (override)
 
         // 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.map(d => corrParent.findOverride("cs", d) || d),
-          csDelta.afterIncomingEdges.map(d => corrParent.findOverride("cs", d) || d).concat(corrDeletion));
-        // const csDeletion1 = new NodeDeletion(csCreation, [], [corrDeletion]);
+        const sourceDeletion1 = new NodeDeletion(csCreation,
+          sourceDeletion.deletedOutgoingEdges.map(d => corrParent.findOverride("cs", d) || d),
+          sourceDeletion.afterIncomingEdges.map(d => corrParent.findOverride("cs", d) || d).concat(corrDeletion));
 
-        csOverrides.set(csDelta, csDeletion1);
-        asOverrides.set(asDeletion, asDeletion1);
+        sourceOverrides.set(sourceDeletion, sourceDeletion1);
+        targetOverrides.set(targetDeletion, targetDeletion1);
 
-        asDeltas.push(asDeletion);
+        targetDeltas.push(targetDeletion);
         corrDeltas.push(corrDeletion);
       }
     }
 
-    const asComposite = this.asLvl.createComposite(asDeltas, "parse:"+csComposite.getDescription());
+    const targetComposite = targetLvl.createComposite(targetDeltas, compositeDescriptionPrefix+":"+sourceComposite.getDescription());
 
     // New CORR-version:
-    const csDeltas1 = csDeltas.map(d => csOverrides.get(d) || d);
-    const asDeltas1 = asDeltas.map(d => asOverrides.get(d) || d);
+    const sourceDeltas1 = sourceDeltas.map(d => sourceOverrides.get(d) || d);
+    const targetDeltas1 = targetDeltas.map(d => targetOverrides.get(d) || d);
 
     // the order in which corr-deltas are put in corrComposite matters - deltas must occur after their dependencies:
     const orderedByDependency: Delta[] = [];
     visitPartialOrdering(
-      [...csDeltas1, ...asDeltas1, ...corrDeltas],
+      [...sourceDeltas1, ...targetDeltas1, ...corrDeltas],
       (d: Delta) => d.getDependencies(),
       (d: Delta) => orderedByDependency.push(d));
-    const corrComposite = this.corrLvl.createComposite(orderedByDependency, "corr-parse:"+csComposite.getDescription());
+    const corrComposite = this.corrLvl.createComposite(orderedByDependency, "corr-"+compositeDescriptionPrefix+":"+sourceComposite.getDescription());
     return {
-      corr: corrComposite,
-      as: asComposite,
-      csOverrides,
-      asOverrides,
+      corrComposite,
+      targetComposite,
+      sourceOverrides,
+      targetOverrides,
     };
   }
+
+  parse(csComposite: CompositeDelta, csParent: Version, corrParent: Version, asParent): ParseOrRenderResult {
+    const {
+      corrComposite,
+      targetComposite,
+      sourceOverrides: csOverrides,
+      targetOverrides: asOverrides,
+    } = this.propagate_change(csComposite, csParent, corrParent, asParent, this.asLvl, "parse");
+
+    return { corrComposite, targetComposite, csOverrides, asOverrides };
+  }
+
+  render(asComposite: CompositeDelta, csParent: Version, corrParent: Version, asParent: Version): ParseOrRenderResult {
+    const {
+      corrComposite,
+      targetComposite,
+      sourceOverrides: asOverrides,
+      targetOverrides: csOverrides,
+    } = this.propagate_change(asComposite, asParent, corrParent, csParent, this.csLvl, "render");
+
+    return { corrComposite, targetComposite, csOverrides, asOverrides };
+  }
 }