d2compiler: Integrate d2ir (wip)
This commit is contained in:
parent
5af31670d1
commit
7d011bab47
4 changed files with 98 additions and 75 deletions
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
}, {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
33
d2ir/d2ir.go
33
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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue