Last active
October 22, 2019 16:15
-
-
Save JasonKleban/e0b8ab1ef0a6baf5148b6d0ba33bbd7c to your computer and use it in GitHub Desktop.
raph.ts - DSL for specifying immutable graphs of nodes and edges.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* DSL for specifying graphs of nodes and edges. | |
* Graphs are immutable and create a new graph object for each operation. | |
* Graphs are stateful about a subject node and a subject edge for purposes of | |
* creating new edges and adding attributes to the subject node or subject edge. | |
* From the perspective of a user DSL code listing, the last _mentioned_ node is the | |
* subject node to which new edges or attributes are added and the last mentioned | |
* edge is the subject edge to which attributes are added. | |
*/ | |
namespace Raph { | |
interface G<NA, EA> { | |
/** Focus on a node by key. If the node key wasn't known, it is added. */ | |
n(key: string): Gn<NA, EA> | |
readonly nodes: N<NA>[] | |
readonly edges: E<EA>[] | |
dot(): string; | |
} | |
interface Gn<NA, EA> extends G<NA, EA> { | |
/** Add an edge between the subject node and the node by key. If the node key wasn't known, it is added. */ | |
en(key: string): Gn<NA, EA> & Ge<NA, EA> | |
/** Set the attributes of the subject node */ | |
na(attributes : NA) : Gn<NA, EA> | |
} | |
interface Ge<NA, EA> extends Gn<NA, EA> { | |
/** Set the attributes of the subject edge */ | |
ea(attributes : EA) : Gn<NA, EA> & Ge<NA, EA> | |
} | |
interface N<NA> { | |
key: string | |
attributes?: NA | |
} | |
interface E<EA> { | |
l: string | |
r: string | |
attributes?: EA | |
} | |
class Graph<NA, EA> implements G<NA, EA> { | |
constructor ( | |
public readonly nodes: N<NA>[] = [], | |
public readonly edges: E<EA>[] = []) { } | |
n(key: string): Gn<NA, EA> { | |
return new GraphWithSubjNode<NA, EA>( | |
!!(this.nodes || []).filter(n => n.key === key)[0] | |
? this.nodes | |
: [ ... this.nodes, { key } ], | |
this.edges, | |
key | |
); | |
} | |
dot = () => `digraph G {\n${ "".concat(... this.edges.map(e => ` "${e.l}" -> "${e.r}"\n`)) }}`; | |
} | |
class GraphWithSubjNode<NA, EA> extends Graph<NA, EA> implements Gn<NA, EA> { | |
constructor ( | |
nodes: N<NA>[], | |
edges: E<EA>[], | |
public readonly subjectNodeKey: string) { | |
super(nodes, edges); | |
} | |
en(key: string): Gn<NA, EA> & Ge<NA, EA> { | |
const newEdge : E<EA> = { | |
l: this.subjectNodeKey, | |
r: key | |
} | |
return new GraphWithSubjEdge<NA, EA>( | |
!!(this.nodes || []).filter(n => n.key === key)[0] | |
? this.nodes | |
: [ ... this.nodes, { key } ], | |
[ | |
... this.edges, | |
newEdge | |
], | |
key, | |
newEdge | |
); | |
} | |
na(attributes: NA): Gn<NA, EA> { | |
const newNodes = this.nodes.slice(); | |
newNodes.splice( | |
this.nodes.findIndex(n => n.key === this.subjectNodeKey), | |
1, | |
{ key: this.subjectNodeKey, attributes }); | |
return new GraphWithSubjNode<NA, EA>( | |
newNodes, | |
this.edges, | |
this.subjectNodeKey | |
); | |
} | |
} | |
class GraphWithSubjEdge<NA, EA> extends GraphWithSubjNode<NA, EA> implements Ge<NA, EA> { | |
constructor ( | |
nodes: N<NA>[], | |
edges: E<EA>[], | |
subjectNodeKey: string, | |
public readonly subjectEdge: E<EA>) { | |
super(nodes, edges, subjectNodeKey); | |
} | |
ea(attributes: EA): Gn<NA, EA> & Ge<NA, EA> { | |
const newEdgesWithSubjectEdge = this.edges.map(e => | |
e !== this.subjectEdge | |
? { e, isSubjectEdge: false } | |
: { | |
e: { | |
l: e.l, | |
r: e.r, | |
attributes | |
}, | |
isSubjectEdge: true | |
}); | |
const newEdges = newEdgesWithSubjectEdge.map(({ e }) => e); | |
const newSubjectEdge = newEdgesWithSubjectEdge.map(({ e }) => e)[0]; | |
return new GraphWithSubjEdge<NA, EA>( | |
this.nodes, | |
newEdges, | |
this.subjectNodeKey, | |
newSubjectEdge | |
); | |
} | |
} | |
export const g = <NA = void, EA = void>() : G<NA, EA> => new Graph<NA, EA>(); | |
export const from = <NA = void, EA = void>(obj: unknown): G<NA, EA> => { | |
const obj_ = obj as any; | |
return new Graph<NA, EA>( | |
obj_.nodes.map((n : any) => ({ key: n.key, attributes: n.attributes })), | |
obj_.edges.map((e : any) => ({ l: e.l, r: e.r, attributes: e.attributes })) | |
) | |
} | |
} | |
const x = Raph.g<{ label: string }, { label: string }>() | |
.n("A").en("B").en("C").ea({ label: "bc" }) | |
.n("D").na({ label: "d" }) | |
.n("A").en("D"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment