From 09d1ec63148c71a7841b861985758e0c94f88cac Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Sun, 25 Jun 2023 23:10:56 -0700 Subject: [PATCH] reimplement --- d2compiler/compile.go | 19 ++- d2compiler/compile_test.go | 33 ++++ d2graph/d2graph.go | 59 +++---- d2ir/compile.go | 8 +- d2ir/d2ir.go | 14 ++ .../TestCompile2/nulls/basic/edge.exp.json | 133 +-------------- .../nulls/implicit/connection.exp.json | 156 ++++++++++++++++++ .../nulls/reappear/shape.exp.json | 2 +- 8 files changed, 244 insertions(+), 180 deletions(-) create mode 100644 testdata/d2compiler/TestCompile2/nulls/implicit/connection.exp.json diff --git a/d2compiler/compile.go b/d2compiler/compile.go index 2486567f9..581c1beb5 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -78,6 +78,7 @@ func (c *compiler) compileBoard(g *d2graph.Graph, ir *d2ir.Map) *d2graph.Graph { ir = ir.Copy(nil).(*d2ir.Map) // c.preprocessSeqDiagrams(ir) c.compileMap(g.Root, ir) + c.nullify(g) if len(c.err.Errors) == 0 { c.validateKeys(g.Root, ir) } @@ -244,7 +245,7 @@ func (c *compiler) compileMap(obj *d2graph.Object, m *d2ir.Map) { } for _, e := range m.Edges { - c.compileEdge(obj, e) + c.compileEdge(obj, e, m) } } } @@ -303,11 +304,6 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) { } } - if f.Primary() != nil { - if _, ok := f.Primary().Value.(*d2ast.Null); ok { - return - } - } obj = obj.EnsureChild(d2graphIDA([]string{f.Name})) if f.Primary() != nil { @@ -708,12 +704,13 @@ func compileStyleFieldInit(attrs *d2graph.Attributes, f *d2ir.Field) { } } -func (c *compiler) compileEdge(obj *d2graph.Object, e *d2ir.Edge) { +func (c *compiler) compileEdge(obj *d2graph.Object, e *d2ir.Edge, m *d2ir.Map) { if e.Primary() != nil { if _, ok := e.Primary().Value.(*d2ast.Null); ok { return } } + edge, err := obj.Connect(d2graphIDA(e.ID.SrcPath), d2graphIDA(e.ID.DstPath), e.ID.SrcArrow, e.ID.DstArrow, "") if err != nil { c.errorf(e.References[0].AST(), err.Error()) @@ -844,6 +841,14 @@ func (c *compiler) compileArrowheads(edge *d2graph.Edge, f *d2ir.Field) { } } +func (c *compiler) nullify(g *d2graph.Graph) { + for _, obj := range g.Objects { + if len(obj.References) > 0 && obj.References[len(obj.References)-1].Nulled() { + g.DeleteObject(obj) + } + } +} + // TODO add more, e.g. C, bash var ShortToFullLanguageAliases = map[string]string{ "md": "markdown", diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 82fd3f19c..9ce23f5bf 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -3001,6 +3001,39 @@ a }) } }) + + t.Run("implicit", func(t *testing.T) { + t.Parallel() + + tca := []struct { + name string + skip bool + run func(t *testing.T) + }{ + { + name: "connection", + run: func(t *testing.T) { + g := assertCompile(t, ` +x -> y +y: null +`, "") + assert.Equal(t, 1, len(g.Objects)) + assert.Equal(t, 0, len(g.Edges)) + }, + }, + } + + for _, tc := range tca { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + if tc.skip { + t.SkipNow() + } + tc.run(t) + }) + } + }) } func assertCompile(t *testing.T, text string, expErr string) *d2graph.Graph { diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 27773d75a..f86eb8f4e 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -73,6 +73,28 @@ func (g *Graph) RootBoard() *Graph { return g } +// DeleteObject deletes an object along with all its descendants. +// It also deletes all edges connected to it or a descendant. +func (g *Graph) DeleteObject(obj *Object) { + for i, obj2 := range g.Objects { + if obj == obj2 { + g.Objects = append(g.Objects[:i], g.Objects[i+1:]...) + i-- + } + } + obj.Parent.removeChild(obj) + + for i, e := range g.Edges { + if e.Src == obj || e.Dst == obj { + g.Edges = append(g.Edges[:i], g.Edges[i+1:]...) + } + } + + for _, ch := range obj.ChildrenArray { + g.DeleteObject(ch) + } +} + type LayoutGraph func(context.Context, *Graph) error // TODO consider having different Scalar types @@ -193,6 +215,10 @@ type Reference struct { ScopeAST *d2ast.Map `json:"-"` } +func (r *Reference) Nulled() bool { + return r.MapKey != nil && r.MapKey.Value.Null != nil +} + func (r Reference) MapKeyEdgeDest() bool { return r.Key == r.MapKey.Edges[r.MapKeyEdgeIndex].Dst } @@ -785,39 +811,6 @@ func (obj *Object) ensureChildEdge(ida []string) *Object { return obj } -// DeleteChild deletes a child from the graph. -// If it has connections or children, those will also be deleted (recursively) -// func (obj *Object) DeleteChild(ida []string) error { -// if len(ida) == 0 { -// return nil -// } -// -// _, is := ReservedKeywordHolders[ida[0]] -// if len(ida) == 1 && !is { -// _, ok := ReservedKeywords[ida[0]] -// if ok { -// return obj -// } -// } -// -// id := ida[0] -// ida = ida[1:] -// -// if id == "_" { -// return obj.Parent.EnsureChild(ida) -// } -// -// child, ok := obj.Children[strings.ToLower(id)] -// if !ok { -// child = obj.newObject(id) -// } -// -// if len(ida) >= 1 { -// return child.EnsureChild(ida) -// } -// return child -// } - // EnsureChild grabs the child by ids or creates it if it does not exist including all // intermediate nodes. func (obj *Object) EnsureChild(ida []string) *Object { diff --git a/d2ir/compile.go b/d2ir/compile.go index 7c5ed5fb0..be97b62f2 100644 --- a/d2ir/compile.go +++ b/d2ir/compile.go @@ -156,18 +156,12 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext) return } - if f.Primary() != nil { - // null only applies if it's the appearance - if _, ok := f.Primary().Value.(*d2ast.Null); ok { - f.Primary_ = nil - } - } - if refctx.Key.Primary.Unbox() != nil { f.Primary_ = &Scalar{ parent: f, Value: refctx.Key.Primary.Unbox(), } + } if refctx.Key.Value.Array != nil { a := &Array{ diff --git a/d2ir/d2ir.go b/d2ir/d2ir.go index 7234256f7..4ff2ddac4 100644 --- a/d2ir/d2ir.go +++ b/d2ir/d2ir.go @@ -786,6 +786,20 @@ func (m *Map) GetEdges(eid *EdgeID) []*Edge { return ea } +func (m *Map) DeleteEdge(eid *EdgeID) *Edge { + if eid == nil { + return nil + } + + for i, e := range m.Edges { + if e.ID.Match(eid) { + m.Edges = append(m.Edges[:i], m.Edges[i+1:]...) + return e + } + } + return nil +} + func (m *Map) CreateEdge(eid *EdgeID, refctx *RefContext) (*Edge, error) { if ParentEdge(m) != nil { return nil, d2parser.Errorf(refctx.Edge, "cannot create edge inside edge") diff --git a/testdata/d2compiler/TestCompile2/nulls/basic/edge.exp.json b/testdata/d2compiler/TestCompile2/nulls/basic/edge.exp.json index 9f83b57fc..842a64237 100644 --- a/testdata/d2compiler/TestCompile2/nulls/basic/edge.exp.json +++ b/testdata/d2compiler/TestCompile2/nulls/basic/edge.exp.json @@ -132,138 +132,7 @@ "zIndex": 0 }, "edges": null, - "objects": [ - { - "id": "a", - "id_val": "a", - "references": [ - { - "key": { - "range": "d2/testdata/d2compiler/TestCompile2/nulls/basic/edge.d2,1:0:1-1:1:2", - "path": [ - { - "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/nulls/basic/edge.d2,1:0:1-1:1:2", - "value": [ - { - "string": "a", - "raw_string": "a" - } - ] - } - } - ] - }, - "key_path_index": 0, - "map_key_edge_index": 0 - }, - { - "key": { - "range": "d2/testdata/d2compiler/TestCompile2/nulls/basic/edge.d2,2:1:9-2:2:10", - "path": [ - { - "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/nulls/basic/edge.d2,2:1:9-2:2:10", - "value": [ - { - "string": "a", - "raw_string": "a" - } - ] - } - } - ] - }, - "key_path_index": 0, - "map_key_edge_index": 0 - } - ], - "attributes": { - "label": { - "value": "a" - }, - "labelDimensions": { - "width": 0, - "height": 0 - }, - "style": {}, - "near_key": null, - "shape": { - "value": "rectangle" - }, - "direction": { - "value": "" - }, - "constraint": null - }, - "zIndex": 0 - }, - { - "id": "b", - "id_val": "b", - "references": [ - { - "key": { - "range": "d2/testdata/d2compiler/TestCompile2/nulls/basic/edge.d2,1:5:6-1:6:7", - "path": [ - { - "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/nulls/basic/edge.d2,1:5:6-1:6:7", - "value": [ - { - "string": "b", - "raw_string": "b" - } - ] - } - } - ] - }, - "key_path_index": 0, - "map_key_edge_index": 0 - }, - { - "key": { - "range": "d2/testdata/d2compiler/TestCompile2/nulls/basic/edge.d2,2:6:14-2:7:15", - "path": [ - { - "unquoted_string": { - "range": "d2/testdata/d2compiler/TestCompile2/nulls/basic/edge.d2,2:6:14-2:7:15", - "value": [ - { - "string": "b", - "raw_string": "b" - } - ] - } - } - ] - }, - "key_path_index": 0, - "map_key_edge_index": 0 - } - ], - "attributes": { - "label": { - "value": "b" - }, - "labelDimensions": { - "width": 0, - "height": 0 - }, - "style": {}, - "near_key": null, - "shape": { - "value": "rectangle" - }, - "direction": { - "value": "" - }, - "constraint": null - }, - "zIndex": 0 - } - ] + "objects": null }, "err": null } diff --git a/testdata/d2compiler/TestCompile2/nulls/implicit/connection.exp.json b/testdata/d2compiler/TestCompile2/nulls/implicit/connection.exp.json new file mode 100644 index 000000000..bfead385b --- /dev/null +++ b/testdata/d2compiler/TestCompile2/nulls/implicit/connection.exp.json @@ -0,0 +1,156 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2compiler/TestCompile2/nulls/implicit/connection.d2,0:0:0-3:0:16", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/nulls/implicit/connection.d2,1:0:1-1:6:7", + "edges": [ + { + "range": "d2/testdata/d2compiler/TestCompile2/nulls/implicit/connection.d2,1:0:1-1:6:7", + "src": { + "range": "d2/testdata/d2compiler/TestCompile2/nulls/implicit/connection.d2,1:0:1-1:1:2", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/nulls/implicit/connection.d2,1:0:1-1:1:2", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "src_arrow": "", + "dst": { + "range": "d2/testdata/d2compiler/TestCompile2/nulls/implicit/connection.d2,1:5:6-1:6:7", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/nulls/implicit/connection.d2,1:5:6-1:6:7", + "value": [ + { + "string": "y", + "raw_string": "y" + } + ] + } + } + ] + }, + "dst_arrow": ">" + } + ], + "primary": {}, + "value": {} + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/nulls/implicit/connection.d2,2:0:8-2:7:15", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/nulls/implicit/connection.d2,2:0:8-2:1:9", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/nulls/implicit/connection.d2,2:0:8-2:1:9", + "value": [ + { + "string": "y", + "raw_string": "y" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "null": { + "range": "d2/testdata/d2compiler/TestCompile2/nulls/implicit/connection.d2,2:3:11-2:7:15" + } + } + } + } + ] + }, + "root": { + "id": "", + "id_val": "", + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + "edges": null, + "objects": [ + { + "id": "x", + "id_val": "x", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/nulls/implicit/connection.d2,1:0:1-1:1:2", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/nulls/implicit/connection.d2,1:0:1-1:1:2", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "x" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ] + }, + "err": null +} diff --git a/testdata/d2compiler/TestCompile2/nulls/reappear/shape.exp.json b/testdata/d2compiler/TestCompile2/nulls/reappear/shape.exp.json index cd14b3174..e8a97f770 100644 --- a/testdata/d2compiler/TestCompile2/nulls/reappear/shape.exp.json +++ b/testdata/d2compiler/TestCompile2/nulls/reappear/shape.exp.json @@ -172,7 +172,7 @@ ], "attributes": { "label": { - "value": "a" + "value": "" }, "labelDimensions": { "width": 0,