d2compiler: Integrate d2ir (wip)

This commit is contained in:
Anmol Sethi 2023-01-23 22:45:21 -08:00
parent 5af31670d1
commit 7d011bab47
No known key found for this signature in database
GPG key ID: 25BC68888A99A8BA
4 changed files with 98 additions and 75 deletions

View file

@ -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
}

View file

@ -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`,
}, {

View file

@ -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)

View file

@ -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 {