Browse Source

WIP: Rountangle -> Statecharts parser

Joeri Exelmans 2 years ago
parent
commit
7d7698fa87
2 changed files with 93 additions and 73 deletions
  1. 34 39
      src/frontend/app.tsx
  2. 59 34
      src/parser/trivial_parser.ts

+ 34 - 39
src/frontend/app.tsx

@@ -84,6 +84,10 @@ export interface VersionedModelProps {
 
 
   state: VersionedModelState;
   state: VersionedModelState;
   setState: (callback: (VersionedModelState) => VersionedModelState) => void;
   setState: (callback: (VersionedModelState) => VersionedModelState) => void;
+
+  defaultTab0: string;
+  defaultTab1: string;
+  tabs: string[];
 }
 }
 
 
 class VersionedModel extends React.Component<VersionedModelProps, {}> {
 class VersionedModel extends React.Component<VersionedModelProps, {}> {
@@ -160,24 +164,22 @@ class VersionedModel extends React.Component<VersionedModelProps, {}> {
       );
       );
     }
     }
 
 
-    const makeConcreteSyntaxTabs = defaultTab => (
+    const makeTabs = (defaultTab, tabs) => (
       <Tabs defaultValue={defaultTab} keepMounted={false}>
       <Tabs defaultValue={defaultTab} keepMounted={false}>
-        {this.props.readonly?(
-          <Tabs.List>
-            <Tabs.Tab value="state">State</Tabs.Tab>
-            <Tabs.Tab value="history">History</Tabs.Tab>
-            <Tabs.Tab value="dependencyL1">Deltas (L1)</Tabs.Tab>
-            <Tabs.Tab value="dependencyL0">Deltas (L0)</Tabs.Tab>
-          </Tabs.List>
-        ):(
-          <Tabs.List>
-            <Tabs.Tab value="editor">Editor</Tabs.Tab>
-            <Tabs.Tab value="state">State</Tabs.Tab>
-            <Tabs.Tab value="history">History</Tabs.Tab>
-            <Tabs.Tab value="dependencyL1">Deltas (L1)</Tabs.Tab>
-            <Tabs.Tab value="dependencyL0">Deltas (L0)</Tabs.Tab>
-          </Tabs.List>
-        )}
+        <Tabs.List>
+          {tabs.map(tab => {
+            if (tab === "editor")
+              return <Tabs.Tab key={tab} value="editor">Editor</Tabs.Tab>;
+            if (tab === "state")
+              return <Tabs.Tab key={tab} value="state">State</Tabs.Tab>;
+            if (tab === "history")
+              return <Tabs.Tab key={tab} value="history">History</Tabs.Tab>;
+            if (tab === "dependencyL1")
+              return <Tabs.Tab key={tab} value="dependencyL1">Deltas (L1)</Tabs.Tab>;
+            if (tab === "dependencyL0")
+              return <Tabs.Tab key={tab} value="dependencyL0">Deltas (L0)</Tabs.Tab>;
+          })}
+        </Tabs.List>
 
 
         <Tabs.Panel value="state">
         <Tabs.Panel value="state">
           {makeInfoHoverCardIcon(<>
           {makeInfoHoverCardIcon(<>
@@ -263,10 +265,8 @@ class VersionedModel extends React.Component<VersionedModelProps, {}> {
       <>
       <>
         <Title order={4}>{this.props.title}</Title>
         <Title order={4}>{this.props.title}</Title>
         <Stack>
         <Stack>
-          {/*{makeConcreteSyntaxTabs(this.props.readonly?"state":"editor")}*/}
-          {/*{makeConcreteSyntaxTabs("history")}*/}
-          {makeConcreteSyntaxTabs("state")}
-          {makeConcreteSyntaxTabs("dependencyL1")}
+          {makeTabs(this.props.defaultTab0, this.props.tabs)}
+          {makeTabs(this.props.defaultTab1, this.props.tabs)}
         </Stack>
         </Stack>
 
 
         <SimpleGrid cols={2}>
         <SimpleGrid cols={2}>
@@ -439,6 +439,8 @@ export class App extends React.Component<{}, AppState> {
           mapping, parse: boolean) => {
           mapping, parse: boolean) => {
       return (sourceDeltas: PrimitiveDelta[], description: string) => {
       return (sourceDeltas: PrimitiveDelta[], description: string) => {
 
 
+        const targetDescription = (parse ? "parse:" : "render:") + description;
+
         const sourceParent = sourceState.version;
         const sourceParent = sourceState.version;
         const {corr: corrParent, target: targetParent} = mapping.get(sourceParent) || (()=>{throw new Error("X")})();
         const {corr: corrParent, target: targetParent} = mapping.get(sourceParent) || (()=>{throw new Error("X")})();
 
 
@@ -451,26 +453,10 @@ export class App extends React.Component<{}, AppState> {
 
 
         console.log({corrDeltas})
         console.log({corrDeltas})
 
 
-        // if (parse) {
-        //   const {corrDeltas: corrDeltasExtra, targetDeltas: targetDeltasExtra, sourceOverrides: sourceOverridesExtra, targetOverrides: targetOverridesExtra} =
-        //   (new InsidenessParser(this.primitiveRegistry, this.generateUUID)).parse(sourceDeltas, sourceGraphState, corrGraphState, targetGraphState);
-        //   console.log({corrDeltasExtra})
-        //   corrDeltas.push(...corrDeltasExtra);
-        //   targetDeltas.push(...targetDeltasExtra);
-        //   for (const [key,val] of sourceOverridesExtra.entries()) {
-        //     if (sourceOverrides.has(key)) throw new Error("Double override");
-        //     sourceOverrides.set(key, val);
-        //   }
-        //   for (const [key,val] of targetOverridesExtra.entries()) {
-        //     if (targetOverrides.has(key)) throw new Error("Double override");
-        //     targetOverrides.set(key, val);
-        //   }
-        // }
-
         const sourceComposite = sourceState.compositeLevel.createComposite(sourceDeltas, description);
         const sourceComposite = sourceState.compositeLevel.createComposite(sourceDeltas, description);
         const newSourceVersion = sourceState.versionRegistry.createVersion(sourceState.version, sourceComposite);
         const newSourceVersion = sourceState.versionRegistry.createVersion(sourceState.version, sourceComposite);
 
 
-        const targetComposite = targetState.compositeLevel.createComposite(targetDeltas, description);
+        const targetComposite = targetState.compositeLevel.createComposite(targetDeltas, targetDescription);
         const newTargetVersion =  (targetComposite.deltas.length > 0) ?
         const newTargetVersion =  (targetComposite.deltas.length > 0) ?
           targetState.versionRegistry.createVersion(targetParent, targetComposite)
           targetState.versionRegistry.createVersion(targetParent, targetComposite)
           : targetState.version; // do not create a new target version
           : targetState.version; // do not create a new target version
@@ -493,7 +479,7 @@ export class App extends React.Component<{}, AppState> {
 
 
         console.log({corrDeltasOrderedByDependency});
         console.log({corrDeltasOrderedByDependency});
 
 
-        const corrComposite = this.state.corr.compositeLevel.createComposite(corrDeltasOrderedByDependency, description);
+        const corrComposite = this.state.corr.compositeLevel.createComposite(corrDeltasOrderedByDependency, targetDescription);
         const newCorrVersion = this.state.corr.versionRegistry.createVersion(corrParent, corrComposite,
         const newCorrVersion = this.state.corr.versionRegistry.createVersion(corrParent, corrComposite,
           embed(
           embed(
             ["cs", newCsVersion, csOverrides],
             ["cs", newCsVersion, csOverrides],
@@ -528,6 +514,9 @@ export class App extends React.Component<{}, AppState> {
           <VersionedModel title="Concrete Syntax"
           <VersionedModel title="Concrete Syntax"
             generateUUID={this.generateUUID}
             generateUUID={this.generateUUID}
             primitiveRegistry={this.primitiveRegistry}
             primitiveRegistry={this.primitiveRegistry}
+            tabs={["editor", "state", "history", "dependencyL1", "dependencyL0"]}
+            defaultTab0="editor"
+            defaultTab1="history"
             state={this.state.cs}
             state={this.state.cs}
             setState={this.setCsState.bind(this)}
             setState={this.setCsState.bind(this)}
             setNextNodePosition={(x,y)=>{this.state.cs.d3StateUpdater.x = x; this.state.cs.d3StateUpdater.y = y;}}
             setNextNodePosition={(x,y)=>{this.state.cs.d3StateUpdater.x = x; this.state.cs.d3StateUpdater.y = y;}}
@@ -541,6 +530,9 @@ export class App extends React.Component<{}, AppState> {
           <VersionedModel title="Correspondence" readonly
           <VersionedModel title="Correspondence" readonly
             generateUUID={this.generateUUID}
             generateUUID={this.generateUUID}
             primitiveRegistry={this.primitiveRegistry}
             primitiveRegistry={this.primitiveRegistry}
+            tabs={["state", "history", "dependencyL1", "dependencyL0"]}
+            defaultTab0="state"
+            defaultTab1="history"
             setNextNodePosition={(x,y)=>{this.state.corr.d3StateUpdater.x = x; this.state.corr.d3StateUpdater.y = y;}}
             setNextNodePosition={(x,y)=>{this.state.corr.d3StateUpdater.x = x; this.state.corr.d3StateUpdater.y = y;}}
             state={this.state.corr}
             state={this.state.corr}
             setState={this.setCorrState.bind(this)}
             setState={this.setCorrState.bind(this)}
@@ -554,6 +546,9 @@ export class App extends React.Component<{}, AppState> {
           <VersionedModel title="Abstract Syntax"
           <VersionedModel title="Abstract Syntax"
             generateUUID={this.generateUUID}
             generateUUID={this.generateUUID}
             primitiveRegistry={this.primitiveRegistry}
             primitiveRegistry={this.primitiveRegistry}
+            tabs={["state", "history", "dependencyL1", "dependencyL0"]}
+            defaultTab0="state"
+            defaultTab1="history"
             setNextNodePosition={(x,y)=>{this.state.as.d3StateUpdater.x = x; this.state.as.d3StateUpdater.y = y;}}
             setNextNodePosition={(x,y)=>{this.state.as.d3StateUpdater.x = x; this.state.as.d3StateUpdater.y = y;}}
             state={this.state.as}
             state={this.state.as}
             setState={this.setAsState.bind(this)}
             setState={this.setAsState.bind(this)}

+ 59 - 34
src/parser/trivial_parser.ts

@@ -178,68 +178,93 @@ export class TrivialParser  {
               catch(e) {}
               catch(e) {}
             }
             }
           }
           }
-          function findCorrespondingAsNode(csNode: INodeState) {
-            const pair = csNode.getIncomingEdges().find(([label]) => label==="cs");
+          function findCorrespondingAsNode(csNode: INodeState, reverse: boolean = false) {
+            const pair = csNode.getIncomingEdges().find(([label]) => label===(reverse?"as":"cs"));
             if (pair === undefined) {
             if (pair === undefined) {
               throw new Error("No incoming 'cs' edge.");
               throw new Error("No incoming 'cs' edge.");
             }
             }
             const [_, corrNodeState] = pair;
             const [_, corrNodeState] = pair;
-            const asState = corrNodeState.getOutgoingEdges().get("as");
+            const asState = corrNodeState.getOutgoingEdges().get(reverse?"cs":"as");
             if (asState === undefined) {
             if (asState === undefined) {
               throw new Error("Found correspondence node, but it has no outgoing 'as' edge.")
               throw new Error("Found correspondence node, but it has no outgoing 'as' edge.")
             }
             }
             return asState as INodeState;
             return asState as INodeState;
           }
           }
 
 
-
           if (geometryLabels.includes(label)) {
           if (geometryLabels.includes(label)) {
             const updatedNodeId = edgeCreation.source.id.value;
             const updatedNodeId = edgeCreation.source.id.value;
             const newGeometry = getGeometry(updatedNodeId);
             const newGeometry = getGeometry(updatedNodeId);
             if (newGeometry !== undefined) {
             if (newGeometry !== undefined) {
-              console.log({newGeometry});
-              // compare the new geometry to every other rountangle
-              // let parentId;
-              // let smallestArea = Infinity;
+              const updatedAsNode = findCorrespondingAsNode(corrState.nodes.get(updatedNodeId) as INodeState);
+
+              // 1. Check if existing parent links still hold
+              // 1.a. outgoing parent links
+              const otherAsNodeState = updatedAsNode.getOutgoingEdges().get("hasParent");
+              if (otherAsNodeState !== undefined && otherAsNodeState.type === "node") {
+                const otherCsNodeState = findCorrespondingAsNode(otherAsNodeState, true);
+                const otherNodeId = otherCsNodeState.creation.id.value;
+                const otherGeometry = getGeometry(otherNodeId);
+                if (otherGeometry === undefined || !isInside(newGeometry, otherGeometry)) {
+                  // parent relation no longer holds
+                  console.log("deleting outgoing link...")
+                  const deleteLink = updatedAsNode.getDeltasForSetEdge(this.primitiveRegistry, "hasParent", null); // deletes the edge
+                  applyToState(corrState, deleteLink);
+                  targetDeltas.push(...deleteLink);
+                }
+              }
+              // 1.b. incoming parent links
+              for (const [_, otherAsNodeState] of updatedAsNode.getIncomingEdges().filter(([label, ns]) => label === "hasParent")) {
+                const otherCsNodeState = findCorrespondingAsNode(otherAsNodeState, true);
+                const otherNodeId = otherCsNodeState.creation.id.value;
+                const otherGeometry = getGeometry(otherNodeId);
+                // console.log({otherNodeState, otherNodeId, otherGeometry})
+                if (otherGeometry === undefined || !isInside(otherGeometry, newGeometry)) {
+                  // parent relation no longer holds
+                  const otherAsNode = findCorrespondingAsNode(corrState.nodes.get(otherNodeId) as INodeState);
+                  console.log("deleting incoming link...")
+                  const deleteLink = otherAsNode.getDeltasForSetEdge(this.primitiveRegistry, "hasParent", null); // deletes the edge
+                  applyToState(corrState, deleteLink);
+                  targetDeltas.push(...deleteLink);
+                }
+              }
+
+              // 2. Compare the new geometry to every other rountangle
+              let smallestParent: INodeState | null = null;
+              let smallestSurface = Infinity;
               for (const [otherNodeId,otherNodeState] of sourceState.nodes.entries()) {
               for (const [otherNodeId,otherNodeState] of sourceState.nodes.entries()) {
                 if (otherNodeState.creation === edgeCreation.source) {
                 if (otherNodeState.creation === edgeCreation.source) {
                   continue; // don't compare with ourselves
                   continue; // don't compare with ourselves
                 }
                 }
                 const otherGeometry = getGeometry(otherNodeId);
                 const otherGeometry = getGeometry(otherNodeId);
-
-                // Our updated Rountangle may be "inside" many other Rountangles, but we're only interested in the one with the smallest surface area (this should be the direct 'parent', not some further ancestor)
-                // const otherArea = otherGeometry.w * otherGeometry * h;
-                // if (otherArea < smallestArea) {
-                //   smallestArea = otherArea;
-                //   parentId = otherNodeId;
-                // }
-
                 if (otherGeometry !== undefined) {
                 if (otherGeometry !== undefined) {
-                  console.log({otherGeometry})
                   const inside = isInside(newGeometry, otherGeometry);
                   const inside = isInside(newGeometry, otherGeometry);
+                  if (inside) {
+                    const surface = otherGeometry.w * otherGeometry.h;
+                    if (surface < smallestSurface) {
+                      smallestSurface = surface;
+                      smallestParent = findCorrespondingAsNode(corrState.nodes.get(otherNodeId) as INodeState);
+                    }
+                  }
                   const outside = isInside(otherGeometry, newGeometry);
                   const outside = isInside(otherGeometry, newGeometry);
-                  if (inside || outside) {
-                    console.log(inside, outside);
-                    const updatedAsNode = findCorrespondingAsNode(corrState.nodes.get(updatedNodeId) as INodeState);
+                  if (outside) {
+                    console.log("creating link...")
                     const otherAsNode = findCorrespondingAsNode(corrState.nodes.get(otherNodeId) as INodeState);
                     const otherAsNode = findCorrespondingAsNode(corrState.nodes.get(otherNodeId) as INodeState);
-                    console.log({updatedAsNode, otherAsNode});
-                    const insideAsNode = inside ? updatedAsNode : otherAsNode;
-                    const outsideAsNode = inside ? otherAsNode : updatedAsNode;
-
-                    // if (insideAsNode.getOutgoingEdges().
-
-                    const asParentLink = insideAsNode.getDeltasForSetEdge(this.primitiveRegistry, "hasParent", outsideAsNode.asTarget());
-
-                    applyToState(corrState, asParentLink);
-
-                    targetDeltas.push(...asParentLink);
+                    if (otherAsNode.getOutgoingEdges().get("hasParent") === undefined) {
+                      console.log("creating link...")
+                      const asParentLink = updatedAsNode.getDeltasForSetEdge(this.primitiveRegistry, "hasParent", otherAsNode.asTarget());
+                      applyToState(corrState, asParentLink);
+                      targetDeltas.push(...asParentLink);
+                    }
                   }
                   }
                 }
                 }
               }
               }
+              if (smallestParent !== null) {
+                const asParentLink = updatedAsNode.getDeltasForSetEdge(this.primitiveRegistry, "hasParent", smallestParent.asTarget());
+                applyToState(corrState, asParentLink);
+                targetDeltas.push(...asParentLink);
+              }
             }
             }
           }
           }
-          
-
-
         }
         }
       }
       }
     }
     }