From 7d011bab47e33d513741de5dfc6815be2b30f270 Mon Sep 17 00:00:00 2001 From: Anmol Sethi Date: Mon, 23 Jan 2023 22:45:21 -0800 Subject: [PATCH] d2compiler: Integrate d2ir (wip) --- d2compiler/compile.go | 99 +++++++++++++++++++++----------------- d2compiler/compile_test.go | 27 +++++------ d2ir/compile.go | 14 ++---- d2ir/d2ir.go | 33 +++++++++++-- 4 files changed, 98 insertions(+), 75 deletions(-) diff --git a/d2compiler/compile.go b/d2compiler/compile.go index cfaf80f60..4ab8e37b4 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -40,60 +40,62 @@ func Compile(path string, r io.RuneReader, opts *CompileOptions) (*d2graph.Graph return nil, err } - g, err := compileIR(pe, ir.CopyBase(nil)) + g, err := compileIR(pe, ir) if err != nil { return nil, err } g.AST = ast - - err = compileLayersField(pe, g, ir, "layers") - if err != nil { - return nil, err - } - err = compileLayersField(pe, g, ir, "scenarios") - if err != nil { - return nil, err - } - err = compileLayersField(pe, g, ir, "steps") return g, err } -func compileLayersField(pe d2parser.ParseError, g *d2graph.Graph, ir *d2ir.Map, fieldName string) error { - layers := ir.GetField(fieldName) - if layers.Map() == nil { - return nil - } - for _, f := range layers.Map().Fields { - if f.Map() == nil { - continue - } - g2, err := compileIR(pe, f.Map()) - if err != nil { - return err - } - g2.Name = f.Name - g.Layers = append(g.Layers, g2) - } - return nil -} - func compileIR(pe d2parser.ParseError, m *d2ir.Map) (*d2graph.Graph, error) { - g := d2graph.NewGraph() - c := &compiler{ err: pe, } + g := c.compileLayer(m) + if len(c.err.Errors) > 0 { + return nil, c.err + } + return g, nil +} + +func (c *compiler) compileLayer(ir *d2ir.Map) *d2graph.Graph { + g := d2graph.NewGraph() + + m := ir.CopyRoot() c.compileMap(g.Root, m) if len(c.err.Errors) == 0 { c.validateKeys(g.Root, m) } c.validateNear(g) - if len(c.err.Errors) > 0 { - return nil, c.err + c.compileLayersField(g, ir, "layers") + c.compileLayersField(g, ir, "scenarios") + c.compileLayersField(g, ir, "steps") + return g +} + +func (c *compiler) compileLayersField(g *d2graph.Graph, ir *d2ir.Map, fieldName string) { + layers := ir.GetField(fieldName) + if layers.Map() == nil { + return + } + for _, f := range layers.Map().Fields { + if f.Map() == nil { + continue + } + g2 := c.compileLayer(f.Map()) + g2.Name = f.Name + switch fieldName { + case "layers": + g.Layers = append(g.Layers, g2) + case "scenarios": + g.Scenarios = append(g.Scenarios, g2) + case "steps": + g.Steps = append(g.Steps, g2) + } } - return g, nil } type compiler struct { @@ -139,6 +141,9 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) { return } c.compileStyle(obj.Attributes, f.Map()) + if obj.Attributes.Style.Animated != nil { + c.errorf(obj.Attributes.Style.Animated.MapKey, `key "animated" can only be applied to edges`) + } return } @@ -204,7 +209,8 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { c.compileLabel(attrs, f) case "shape": in := d2target.IsShape(scalar.ScalarString()) - if !in { + _, isArrowhead := d2target.Arrowheads[scalar.ScalarString()] + if !in && !isArrowhead { c.errorf(scalar, "unknown shape %q", scalar.ScalarString()) return } @@ -227,6 +233,7 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { c.errorf(scalar, "bad near key %#v: %s", scalar.ScalarString(), err) return } + nearKey.Range = scalar.GetRange() attrs.NearKey = nearKey case "tooltip": attrs.Tooltip = scalar.ScalarString() @@ -378,10 +385,6 @@ func (c *compiler) compileEdgeField(edge *d2graph.Edge, f *d2ir.Field) { return } - if f.Primary() != nil { - c.compileLabel(edge.Attributes, f) - } - if f.Name == "source-arrowhead" || f.Name == "target-arrowhead" { if f.Map() != nil { c.compileArrowheads(edge, f) @@ -399,6 +402,10 @@ func (c *compiler) compileArrowheads(edge *d2graph.Edge, f *d2ir.Field) { attrs = edge.DstArrowhead } + if f.Primary() != nil { + c.compileLabel(attrs, f) + } + for _, f2 := range f.Map().Fields { keyword := strings.ToLower(f2.Name) _, isReserved := d2graph.SimpleReservedKeywords[keyword] @@ -552,10 +559,6 @@ func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) { if obj.Attributes.Shape.Value != d2target.ShapeImage { c.errorf(f.LastPrimaryKey(), "height is only applicable to image shapes.") } - case "3d": - if obj.Attributes.Shape.Value != "" && !strings.EqualFold(obj.Attributes.Shape.Value, d2target.ShapeSquare) && !strings.EqualFold(obj.Attributes.Shape.Value, d2target.ShapeRectangle) { - c.errorf(f.LastPrimaryKey(), `key "3d" can only be applied to squares and rectangles`) - } case "shape": switch obj.Attributes.Shape.Value { case d2target.ShapeSQLTable, d2target.ShapeClass: @@ -578,8 +581,14 @@ func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) { return } + if obj.Attributes.Style.ThreeDee != nil { + if !strings.EqualFold(obj.Attributes.Shape.Value, d2target.ShapeSquare) && !strings.EqualFold(obj.Attributes.Shape.Value, d2target.ShapeRectangle) { + c.errorf(obj.Attributes.Style.ThreeDee.MapKey, `key "3d" can only be applied to squares and rectangles`) + } + } + if obj.Attributes.Shape.Value == d2target.ShapeImage { - c.errorf(obj.Attributes.Shape.MapKey, "image shapes cannot have children.") + c.errorf(f.LastRef().AST(), "image shapes cannot have children.") return } diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index a3ab9d75b..ca993db6c 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -263,7 +263,7 @@ d2/testdata/d2compiler/TestCompile/no_dimensions_on_containers.d2:37:3: height c _ } `, - expErr: `d2/testdata/d2compiler/TestCompile/blank_underscore.d2:3:3: invalid use of parent "_"`, + expErr: `d2/testdata/d2compiler/TestCompile/blank_underscore.d2:3:3: field key must contain more than underscores`, }, { name: "image_non_style", @@ -451,7 +451,7 @@ x: { text: ` _.x `, - expErr: `d2/testdata/d2compiler/TestCompile/underscore_parent_root.d2:2:1: parent "_" cannot be used in the root scope`, + expErr: `d2/testdata/d2compiler/TestCompile/underscore_parent_root.d2:2:1: invalid underscore: no parent`, }, { name: "underscore_parent_middle_path", @@ -461,7 +461,7 @@ x: { y._.z } `, - expErr: `d2/testdata/d2compiler/TestCompile/underscore_parent_middle_path.d2:3:3: parent "_" can only be used in the beginning of paths, e.g. "_.x"`, + expErr: `d2/testdata/d2compiler/TestCompile/underscore_parent_middle_path.d2:3:5: parent "_" can only be used in the beginning of paths, e.g. "_.x"`, }, { name: "underscore_parent_sandwich_path", @@ -471,7 +471,7 @@ x: { _.z._ } `, - expErr: `d2/testdata/d2compiler/TestCompile/underscore_parent_sandwich_path.d2:3:3: parent "_" can only be used in the beginning of paths, e.g. "_.x"`, + expErr: `d2/testdata/d2compiler/TestCompile/underscore_parent_sandwich_path.d2:3:7: parent "_" can only be used in the beginning of paths, e.g. "_.x"`, }, { name: "underscore_edge", @@ -1074,7 +1074,7 @@ x -> y: { space -> stars } `, - expErr: `d2/testdata/d2compiler/TestCompile/nested_edge.d2:1:1: edges cannot be nested within another edge`, + expErr: `d2/testdata/d2compiler/TestCompile/nested_edge.d2:2:3: cannot create edge inside edge`, }, { name: "shape_edge_style", @@ -1340,7 +1340,7 @@ x -> y: { z } `, - expErr: `d2/testdata/d2compiler/TestCompile/edge_map_non_reserved.d2:2:1: edge map keys must be reserved keywords`, + expErr: `d2/testdata/d2compiler/TestCompile/edge_map_non_reserved.d2:3:3: edge map keys must be reserved keywords`, }, { name: "url_link", @@ -1385,7 +1385,7 @@ x -> y: { text: `x.near: txop-center `, - expErr: `d2/testdata/d2compiler/TestCompile/near_bad_constant.d2:1:1: near key "txop-center" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right`, + expErr: `d2/testdata/d2compiler/TestCompile/near_bad_constant.d2:1:9: near key "txop-center" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right`, }, { name: "near_bad_container", @@ -1395,7 +1395,7 @@ x -> y: { y } `, - expErr: `d2/testdata/d2compiler/TestCompile/near_bad_container.d2:1:1: constant near keys cannot be set on shapes with children`, + expErr: `d2/testdata/d2compiler/TestCompile/near_bad_container.d2:2:9: constant near keys cannot be set on shapes with children`, }, { name: "near_bad_connected", @@ -1405,14 +1405,14 @@ x -> y: { } x -> y `, - expErr: `d2/testdata/d2compiler/TestCompile/near_bad_connected.d2:1:1: constant near keys cannot be set on connected shapes`, + expErr: `d2/testdata/d2compiler/TestCompile/near_bad_connected.d2:2:9: constant near keys cannot be set on connected shapes`, }, { name: "nested_near_constant", text: `x.y.near: top-center `, - expErr: `d2/testdata/d2compiler/TestCompile/nested_near_constant.d2:1:1: constant near keys can only be set on root level shapes`, + expErr: `d2/testdata/d2compiler/TestCompile/nested_near_constant.d2:1:11: constant near keys can only be set on root level shapes`, }, { name: "reserved_icon_near_style", @@ -1458,9 +1458,8 @@ y } `, expErr: `d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:3:9: bad icon url "::????:::%%orange": parse "::????:::%%orange": missing protocol scheme -d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:4:18: expected "opacity" to be a number between 0.0 and 1.0 d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:5:18: expected "opacity" to be a number between 0.0 and 1.0 -d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:1:1: near key "y" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right`, +d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:2:9: near key "y" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right`, }, { name: "errors/missing_shape_icon", @@ -1562,7 +1561,7 @@ b`, g.Objects[0].Attributes.Label.Value) GetType(): string style: { opacity: 0.4 - color: blue + font-color: blue } } `, @@ -1661,7 +1660,7 @@ x.y -> a.b: { { name: "3d_oval", - text: `SVP1.style.shape: oval + text: `SVP1.shape: oval SVP1.style.3d: true`, expErr: `d2/testdata/d2compiler/TestCompile/3d_oval.d2:2:1: key "3d" can only be applied to squares and rectangles`, }, { diff --git a/d2ir/compile.go b/d2ir/compile.go index e09bb2836..4c9871b21 100644 --- a/d2ir/compile.go +++ b/d2ir/compile.go @@ -15,17 +15,9 @@ func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) { func Compile(ast *d2ast.Map) (*Map, error) { c := &compiler{} - m := &Map{ - parent: &Field{ - Name: "", - References: []*FieldReference{{ - Context: &RefContext{ - Scope: ast, - }, - }}, - }, - } - m.parent.(*Field).References[0].Context.ScopeMap = m + m := &Map{} + m.initRoot() + m.parent.(*Field).References[0].Context.Scope = ast c.compileMap(m, ast) c.compileScenarios(m) c.compileSteps(m) diff --git a/d2ir/d2ir.go b/d2ir/d2ir.go index 59aee6941..7f59ca207 100644 --- a/d2ir/d2ir.go +++ b/d2ir/d2ir.go @@ -152,6 +152,17 @@ type Map struct { Edges []*Edge `json:"edges"` } +func (m *Map) initRoot() { + m.parent = &Field{ + Name: "", + References: []*FieldReference{{ + Context: &RefContext{ + ScopeMap: m, + }, + }}, + } +} + func (m *Map) Copy(newParent Node) Node { tmp := *m m = &tmp @@ -187,6 +198,13 @@ func (m *Map) CopyBase(newParent Node) *Map { return m2 } +// CopyRoot copies the map such that it is now the root of a diagram. +func (m *Map) CopyRoot() *Map { + m = m.CopyBase(nil) + m.initRoot() + return m +} + // Root reports whether the Map is the root of the D2 tree. func (m *Map) Root() bool { // m.parent exists even on the root map as we store the root AST in @@ -213,9 +231,16 @@ func NodeLayerKind(n Node) LayerKind { var f *Field switch n := n.(type) { case *Field: - f = n + if n.Name == "" { + return LayerLayer + } + f = ParentField(n) case *Map: f = ParentField(n) + if f.Name == "" { + return LayerLayer + } + f = ParentField(f) } if f == nil { return "" @@ -227,11 +252,9 @@ func NodeLayerKind(n Node) LayerKind { return LayerScenario case "steps": return LayerStep - case "": - // root - return LayerLayer + default: + return "" } - return "" } type Field struct {