diff --git a/d2compiler/compile.go b/d2compiler/compile.go index c2a829c7d..3133456dd 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -111,13 +111,13 @@ func (c *compiler) compileMap(obj *d2graph.Object, m *d2ir.Map) { switch obj.Attributes.Shape.Value { case d2target.ShapeClass: - c.compileClass(obj, m) + c.compileClass(obj) case d2target.ShapeSQLTable: - c.compileSQLTable(obj, m) + c.compileSQLTable(obj) } for _, e := range m.Edges { - c.compileEdge(obj, m, e) + c.compileEdge(obj, e) } } @@ -137,7 +137,7 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) { obj = obj.EnsureChild([]string{f.Name}) if f.Primary() != nil { - c.compileLabel(obj, f) + c.compileLabel(obj.Attributes, f) } if f.Map() != nil { c.compileMap(obj, f.Map()) @@ -167,11 +167,11 @@ func (c *compiler) compileLabel(attrs *d2graph.Attributes, f d2ir.Node) { attrs.Label.MapKey = f.LastPrimaryKey() } -func (c *compiler) compileReserved(attrs *d2graph.Attributes, f d2ir.Node) { +func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { scalar := f.Primary().Value switch f.Name { case "label": - c.compileLabel(obj, f) + c.compileLabel(attrs, f) case "shape": in := d2target.IsShape(scalar.ScalarString()) if !in { @@ -233,7 +233,13 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f d2ir.Node) { } } -func (c *compiler) compileStyle(attrs *d2graph.Attributes, f d2ir.Node) { +func (c *compiler) compileStyle(attrs *d2graph.Attributes, m *d2ir.Map) { + for _, f := range m.Fields { + c.compileStyleField(attrs, f) + } +} + +func (c *compiler) compileStyleField(attrs *d2graph.Attributes, f *d2ir.Field) { scalar := f.Primary().Value err := attrs.Style.Apply(f.Name, scalar.ScalarString()) if err != nil { @@ -286,7 +292,7 @@ func (c *compiler) compileStyle(attrs *d2graph.Attributes, f d2ir.Node) { func (c *compiler) compileEdge(obj *d2graph.Object, e *d2ir.Edge) { edge, err := obj.Connect(e.ID.SrcPath, e.ID.DstPath, e.ID.SrcArrow, e.ID.DstArrow, "") if err != nil { - c.errorf(e, err.Error()) + c.errorf(e.References[0].AST(), err.Error()) return } @@ -297,7 +303,7 @@ func (c *compiler) compileEdge(obj *d2graph.Object, e *d2ir.Edge) { for _, f := range e.Map().Fields { _, ok := d2graph.ReservedKeywords[f.Name] if !ok { - c.errorf(mk, `edge map keys must be reserved keywords`) + c.errorf(f.References[0].AST(), `edge map keys must be reserved keywords`) continue } c.compileEdgeField(edge, f) @@ -320,7 +326,7 @@ func (c *compiler) compileEdgeField(edge *d2graph.Edge, f *d2ir.Field) { } if f.Primary() != nil { - c.compileLabel(edge, f) + c.compileLabel(edge.Attributes, f) } if f.Name == "source-arrowhead" || f.Name == "target-arrowhead" { @@ -341,6 +347,7 @@ func (c *compiler) compileArrowheads(edge *d2graph.Edge, f *d2ir.Field) { } for _, f2 := range f.Map().Fields { + keyword := strings.ToLower(f2.Name) _, isReserved := d2graph.ReservedKeywords[keyword] if isReserved { c.compileReserved(attrs, f2) @@ -352,7 +359,7 @@ func (c *compiler) compileArrowheads(edge *d2graph.Edge, f *d2ir.Field) { c.compileStyle(attrs, f2.Map()) continue } else { - c.errorf(mk, `source-arrowhead/target-arrowhead map keys must be reserved keywords`) + c.errorf(f2.LastRef().AST(), `source-arrowhead/target-arrowhead map keys must be reserved keywords`) continue } } @@ -370,24 +377,7 @@ var ShortToFullLanguageAliases = map[string]string{ } var FullToShortLanguageAliases map[string]string -func (c *compiler) compileShapes(obj *d2graph.Object) { - for _, obj := range obj.ChildrenArray { - switch obj.Attributes.Shape.Value { - case d2target.ShapeClass: - c.compileClass(obj) - case d2target.ShapeSQLTable: - c.compileSQLTable(obj) - } - c.compileShapes(obj) - } -} - func (c *compiler) compileClass(obj *d2graph.Object) { - if len(m.Edges) > 0 { - c.errorf(m.Edges[0].LastAST(), "class shapes cannot have edges inside") - return - } - obj.Class = &d2target.Class{} for _, f := range obj.ChildrenArray { visiblity := "public" @@ -436,15 +426,7 @@ func (c *compiler) compileClass(obj *d2graph.Object) { } func (c *compiler) compileSQLTable(obj *d2graph.Object) { - if len(m.Edges) > 0 { - c.errorf(m.Edges[0].LastAST(), "sql_table shapes cannot have edges inside") - return - } - obj.SQLTable = &d2target.SQLTable{} - - parentID := obj.Parent.AbsID() - tableIDPrefix := obj.AbsID() + "." for _, col := range obj.ChildrenArray { typ := col.Attributes.Label.Value if typ == col.IDVal { @@ -480,12 +462,13 @@ func (c *compiler) compileSQLTable(obj *d2graph.Object) { } func (c *compiler) validateKeys(obj *d2graph.Object, m *d2ir.Map) { - for _, n := range m.Fields { + for _, f := range m.Fields { c.validateKey(obj, f) } } func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) { + keyword := strings.ToLower(f.Name) _, isReserved := d2graph.ReservedKeywords[keyword] if isReserved { switch obj.Attributes.Shape.Value { @@ -526,7 +509,7 @@ func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) { } default: if len(obj.Children) > 0 && (f.Name == "width" || f.Name == "height") { - c.errorf(f.LastPrimaryKey(), mk.Range.End, fmt.Sprintf("%s cannot be used on container: %s", f.Name, obj.AbsID())) + c.errorf(f.LastPrimaryKey(), fmt.Sprintf("%s cannot be used on container: %s", f.Name, obj.AbsID())) } } @@ -540,12 +523,12 @@ func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) { } if obj.Attributes.Shape.Value == d2target.ShapeImage { - c.errorf(mk, "image shapes cannot have children.") + c.errorf(obj.Attributes.Shape.MapKey, "image shapes cannot have children.") return } - obj = obj.HasChild([]string{f.Name}) - if f.Map() != nil { + obj, ok := obj.HasChild([]string{f.Name}) + if ok && f.Map() != nil { c.validateKeys(obj, f.Map()) } } diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 9e2e9742b..4984c1864 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -973,7 +973,18 @@ func (obj *Object) Connect(srcID, dstID []string, srcArrow, dstArrow bool, label } e.initIndex() + addSQLTableColumnIndexes(e, srcID, dstID, obj, src, dst) + + obj.Graph.Edges = append(obj.Graph.Edges, e) + return e, nil +} + +func addSQLTableColumnIndexes(e *Edge, srcID, dstID []string, obj, src, dst *Object) { if src.Attributes.Shape.Value == d2target.ShapeSQLTable { + if src == dst { + // Ignore edge to column inside table. + return + } objAbsID := obj.AbsIDArray() srcAbsID := src.AbsIDArray() if len(objAbsID) + len(srcID) > len(srcAbsID) { @@ -1001,10 +1012,6 @@ func (obj *Object) Connect(srcID, dstID []string, srcArrow, dstArrow bool, label } } } - - - obj.Graph.Edges = append(obj.Graph.Edges, e) - return e, nil } // TODO: Treat undirectional/bidirectional edge here and in HasEdge flipped. Same with diff --git a/d2ir/compile.go b/d2ir/compile.go index 18278ac97..30f22bc49 100644 --- a/d2ir/compile.go +++ b/d2ir/compile.go @@ -15,7 +15,16 @@ func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) { func Compile(ast *d2ast.Map) (*Map, error) { c := &compiler{} - m := &Map{} + m := &Map{ + parent: &Field{ + Name: "", + References: []*FieldReference{{ + Context: &RefContext{ + Scope: ast, + }, + }}, + }, + } c.compileMap(m, ast) c.compileScenarios(m) c.compileSteps(m) @@ -160,7 +169,7 @@ func (c *compiler) compileEdges(dst *Map, refctx *RefContext) { continue } e = ea[0] - e.References = append(e.References, EdgeReference{ + e.References = append(e.References, &EdgeReference{ Context: refctx, }) dst.appendFieldReferences(0, refctx.Edge.Src, refctx) diff --git a/d2ir/d2ir.go b/d2ir/d2ir.go index ca5a8b5ce..438eaf1c9 100644 --- a/d2ir/d2ir.go +++ b/d2ir/d2ir.go @@ -30,11 +30,6 @@ type Node interface { LastPrimaryKey() *d2ast.Key } -type Reference interface { - reference() - AST() d2ast.Node -} - var _ Node = &Scalar{} var _ Node = &Field{} var _ Node = &Edge{} @@ -109,6 +104,26 @@ func (n *Edge) String() string { return d2format.Format(n.ast()) } func (n *Array) String() string { return d2format.Format(n.ast()) } func (n *Map) String() string { return d2format.Format(n.ast()) } +func (n *Scalar) LastRef() Reference { return parentRef(n) } +func (n *Map) LastRef() Reference { return parentRef(n) } +func (n *Array) LastRef() Reference { return parentRef(n) } + +func (n *Scalar) LastPrimaryKey() *d2ast.Key { return parentPrimaryKey(n) } +func (n *Map) LastPrimaryKey() *d2ast.Key { return parentPrimaryKey(n) } +func (n *Array) LastPrimaryKey() *d2ast.Key { return parentPrimaryKey(n) } + +type Reference interface { + reference() + // Most specific AST node for the reference. + AST() d2ast.Node +} + +var _ Reference = &FieldReference{} +var _ Reference = &EdgeReference{} + +func (r *FieldReference) reference() {} +func (r *EdgeReference) reference() {} + type Scalar struct { parent Node Value d2ast.Scalar `json:"value"` @@ -174,18 +189,14 @@ func (m *Map) CopyBase(newParent Node) *Map { // Root reports whether the Map is the root of the D2 tree. func (m *Map) Root() bool { - return m.parent == nil -} - -func (f *Map) LastRef() *MapReference { - if f.parent == nil { - return nil + // m.parent exists even on the root map as we store the root AST in + // m.parent.References[0].Context.Map for reporting error messages about the whole IR. + // Or if otherwise needed. + f, ok := m.parent.(*Field) + if !ok { + return false } - return f.References[len(f.References)-1] -} - -func (f *Map) LastAST() d2ast.Node { - return f.LastRef().String + return f.Name == "" } type LayerKind string @@ -199,25 +210,26 @@ const ( // NodeLayerKind reports whether n represents the root of a layer. // n should be *Field or *Map func NodeLayerKind(n Node) LayerKind { + var f *Field switch n := n.(type) { case *Field: - n = ParentField(n) - if n != nil { - switch n.Name { - case "layers": - return LayerLayer - case "scenarios": - return LayerScenario - case "steps": - return LayerStep - } - } + f = ParentField(n) case *Map: - f := ParentField(n) - if f == nil { - return LayerLayer - } - return NodeLayerKind(f) + f = ParentField(n) + } + if f == nil { + return "" + } + switch f.Name { + case "layers": + return LayerLayer + case "scenarios": + return LayerScenario + case "steps": + return LayerStep + case "": + // root + return LayerLayer } return "" } @@ -230,7 +242,7 @@ type Field struct { Primary_ *Scalar `json:"primary,omitempty"` Composite Composite `json:"composite,omitempty"` - References []FieldReference `json:"references,omitempty"` + References []*FieldReference `json:"references,omitempty"` } func (f *Field) Copy(newParent Node) Node { @@ -238,7 +250,7 @@ func (f *Field) Copy(newParent Node) Node { f = &tmp f.parent = newParent.(*Map) - f.References = append([]FieldReference(nil), f.References...) + f.References = append([]*FieldReference(nil), f.References...) if f.Primary_ != nil { f.Primary_ = f.Primary_.Copy(f).(*Scalar) } @@ -248,7 +260,7 @@ func (f *Field) Copy(newParent Node) Node { return f } -func (f *Field) LastPrimaryRef() *FieldReference { +func (f *Field) lastPrimaryRef() *FieldReference { inEdge := ParentEdge(f) != nil for i := len(f.References) - 1; i >= 0; i-- { fr := f.References[i] @@ -266,21 +278,17 @@ func (f *Field) LastPrimaryRef() *FieldReference { } func (f *Field) LastPrimaryKey() *d2ast.Key { - fr := f.LastModification() + fr := f.lastPrimaryRef() if fr == nil { return nil } return fr.Context.Key } -func (f *Field) LastRef() *FieldReference { +func (f *Field) LastRef() Reference { return f.References[len(f.References)-1] } -func (f *Field) LastAST() d2ast.Node { - return f.LastRef().String -} - type EdgeID struct { SrcPath []string `json:"src_path"` SrcArrow bool `json:"src_arrow"` @@ -396,7 +404,7 @@ type Edge struct { Primary_ *Scalar `json:"primary,omitempty"` Map_ *Map `json:"map,omitempty"` - References []EdgeReference `json:"references,omitempty"` + References []*EdgeReference `json:"references,omitempty"` } func (e *Edge) Copy(newParent Node) Node { @@ -404,7 +412,7 @@ func (e *Edge) Copy(newParent Node) Node { e = &tmp e.parent = newParent.(*Map) - e.References = append([]EdgeReference(nil), e.References...) + e.References = append([]*EdgeReference(nil), e.References...) if e.Primary_ != nil { e.Primary_ = e.Primary_.Copy(e).(*Scalar) } @@ -414,7 +422,7 @@ func (e *Edge) Copy(newParent Node) Node { return e } -func (e *Edge) LastPrimaryRef() *EdgeReference { +func (e *Edge) lastPrimaryRef() *EdgeReference { for i := len(e.References) - 1; i >= 0; i-- { fr := e.References[i] if fr.Context.Key.EdgeKey == nil { @@ -425,21 +433,17 @@ func (e *Edge) LastPrimaryRef() *EdgeReference { } func (e *Edge) LastPrimaryKey() *d2ast.Key { - fr := f.LastModification() - if fr == nil { + er := e.lastPrimaryRef() + if er == nil { return nil } - return fr.Context.Key + return er.Context.Key } -func (e *Edge) LastRef() *EdgeReference { +func (e *Edge) LastRef() Reference { return e.References[len(e.References)-1] } -func (e *Edge) LastAST() d2ast.Node { - return e.LastRef().Context.Edge -} - type Array struct { parent Node Values []Value `json:"values"` @@ -464,27 +468,39 @@ type FieldReference struct { Context *RefContext `json:"context"` } -func (kr FieldReference) KeyPathIndex() int { - for i, sb := range kr.KeyPath.Path { - if sb.Unbox() == kr.String { +func (fr *FieldReference) KeyPathIndex() int { + for i, sb := range fr.KeyPath.Path { + if sb.Unbox() == fr.String { return i } } panic("d2ir.KeyReference.KeyPathIndex: String not in KeyPath?") } -func (kr FieldReference) EdgeDest() bool { - return kr.KeyPath == kr.Context.Edge.Dst +func (fr *FieldReference) EdgeDest() bool { + return fr.KeyPath == fr.Context.Edge.Dst } -func (kr FieldReference) InEdge() bool { - return kr.Context.Edge != nil +func (fr *FieldReference) InEdge() bool { + return fr.Context.Edge != nil +} + +func (fr *FieldReference) AST() d2ast.Node { + if fr.String == nil { + // Root map. + return fr.Context.Scope + } + return fr.String } type EdgeReference struct { Context *RefContext `json:"context"` } +func (er *EdgeReference) AST() d2ast.Node { + return er.Context.Edge +} + type RefContext struct { Edge *d2ast.Edge `json:"edge"` Key *d2ast.Key `json:"key"` @@ -628,7 +644,7 @@ func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext) (*Field, continue } - f.References = append(f.References, FieldReference{ + f.References = append(f.References, &FieldReference{ String: kp.Path[i].Unbox(), KeyPath: kp, Context: refctx, @@ -651,7 +667,7 @@ func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext) (*Field, f := &Field{ parent: m, Name: head, - References: []FieldReference{{ + References: []*FieldReference{{ String: kp.Path[i].Unbox(), KeyPath: kp, Context: refctx, @@ -773,7 +789,7 @@ func (m *Map) CreateEdge(eid *EdgeID, refctx *RefContext) (*Edge, error) { e := &Edge{ parent: m, ID: eid, - References: []EdgeReference{{ + References: []*EdgeReference{{ Context: refctx, }}, } @@ -868,7 +884,7 @@ func (m *Map) appendFieldReferences(i int, kp *d2ast.KeyPath, refctx *RefContext return } - f.References = append(f.References, FieldReference{ + f.References = append(f.References, &FieldReference{ String: sb.Unbox(), KeyPath: kp, Context: refctx, @@ -945,3 +961,27 @@ func hasLayerKeywords(ida ...string) int { } return -1 } + +func parentRef(n Node) Reference { + f := ParentField(n) + if f != nil { + return f.LastRef() + } + e := ParentEdge(n) + if e != nil { + return e.LastRef() + } + return nil +} + +func parentPrimaryKey(n Node) *d2ast.Key { + f := ParentField(n) + if f != nil { + return f.LastPrimaryKey() + } + e := ParentEdge(n) + if e != nil { + return e.LastPrimaryKey() + } + return nil +} diff --git a/d2ir/d2ir_test.go b/d2ir/d2ir_test.go index cac84e7b3..c8b34dbc0 100644 --- a/d2ir/d2ir_test.go +++ b/d2ir/d2ir_test.go @@ -39,12 +39,10 @@ func TestCopy(t *testing.T) { Composite: a, } e := &d2ir.Edge{ - Primary_: s, Map_: m2, } m := &d2ir.Map{ - Fields: []*d2ir.Field{f}, Edges: []*d2ir.Edge{e}, }