d2ir: References wip

This commit is contained in:
Anmol Sethi 2023-01-17 04:44:14 -08:00
parent 7721c8b2b4
commit f69f401d23
No known key found for this signature in database
GPG key ID: 25BC68888A99A8BA
6 changed files with 190 additions and 74 deletions

View file

@ -651,7 +651,7 @@ type KeyPath struct {
} }
func MakeKeyPath(a []string) *KeyPath { func MakeKeyPath(a []string) *KeyPath {
var kp *KeyPath kp := &KeyPath{}
for _, el := range a { for _, el := range a {
kp.Path = append(kp.Path, MakeValueBox(RawString(el, true)).StringBox()) kp.Path = append(kp.Path, MakeValueBox(RawString(el, true)).StringBox())
} }

View file

@ -50,7 +50,7 @@ func (c *compiler) compileKey(dst *Map, k *d2ast.Key) {
} }
func (c *compiler) compileField(dst *Map, k *d2ast.Key) { func (c *compiler) compileField(dst *Map, k *d2ast.Key) {
f, err := dst.Ensure(d2format.KeyPath(k.Key)) f, err := dst.EnsureField(d2format.KeyPath(k.Key))
if err != nil { if err != nil {
c.errorf(k, err.Error()) c.errorf(k, err.Error())
return return
@ -88,7 +88,7 @@ func (c *compiler) compileField(dst *Map, k *d2ast.Key) {
func (c *compiler) compileEdges(dst *Map, k *d2ast.Key) { func (c *compiler) compileEdges(dst *Map, k *d2ast.Key) {
if k.Key != nil && len(k.Key.Path) > 0 { if k.Key != nil && len(k.Key.Path) > 0 {
f, err := dst.Ensure(d2format.KeyPath(k.Key)) f, err := dst.EnsureField(d2format.KeyPath(k.Key))
if err != nil { if err != nil {
c.errorf(k, err.Error()) c.errorf(k, err.Error())
return return
@ -115,7 +115,17 @@ func (c *compiler) compileEdges(dst *Map, k *d2ast.Key) {
} }
e = ea[0] e = ea[0]
} else { } else {
var err error _, err := dst.EnsureField(eid.SrcPath)
if err != nil {
c.errorf(k.Edges[i].Src, err.Error())
continue
}
_, err = dst.EnsureField(eid.DstPath)
if err != nil {
c.errorf(k.Edges[i].Dst, err.Error())
continue
}
e, err = dst.EnsureEdge(eid) e, err = dst.EnsureEdge(eid)
if err != nil { if err != nil {
c.errorf(k.Edges[i], err.Error()) c.errorf(k.Edges[i], err.Error())
@ -123,17 +133,6 @@ func (c *compiler) compileEdges(dst *Map, k *d2ast.Key) {
} }
} }
_, err := dst.Ensure(eid.SrcPath)
if err != nil {
c.errorf(k.Edges[i].Src, err.Error())
continue
}
_, err = dst.Ensure(eid.DstPath)
if err != nil {
c.errorf(k.Edges[i].Dst, err.Error())
continue
}
if k.EdgeKey != nil { if k.EdgeKey != nil {
if e.Map == nil { if e.Map == nil {
e.Map = &Map{ e.Map = &Map{

View file

@ -79,7 +79,7 @@ func assertField(t testing.TB, n d2ir.Node, nfields, nedges int, primary interfa
var f *d2ir.Field var f *d2ir.Field
if len(ida) > 0 { if len(ida) > 0 {
f = m.Get(ida) f = m.GetField(ida)
if f == nil { if f == nil {
t.Fatalf("expected field %v in map %s", ida, m) t.Fatalf("expected field %v in map %s", ida, m)
} }
@ -255,7 +255,7 @@ func testCompileEdge(t *testing.T) {
t.Parallel() t.Parallel()
tca := []testCase{ tca := []testCase{
{ {
name: "edge", name: "root",
run: func(t testing.TB, m *d2ir.Map) { run: func(t testing.TB, m *d2ir.Map) {
err := parse(t, m, `x -> y`) err := parse(t, m, `x -> y`)
assert.Success(t, err) assert.Success(t, err)
@ -285,14 +285,14 @@ func testCompileEdge(t *testing.T) {
{ {
name: "underscore", name: "underscore",
run: func(t testing.TB, m *d2ir.Map) { run: func(t testing.TB, m *d2ir.Map) {
err := parse(t, m, `x._ -> z`) err := parse(t, m, `p: { _.x -> z }`)
assert.Success(t, err) assert.Success(t, err)
assertField(t, m, 3, 1, nil) assertField(t, m, 3, 1, nil)
assertField(t, m, 0, 0, nil, "x") assertField(t, m, 0, 0, nil, "x")
assertField(t, m, 0, 0, nil, "z") assertField(t, m, 1, 0, nil, "p")
assertEdge(t, m, 0, nil, "(x -> z)[0]") assertEdge(t, m, 0, nil, "(x -> p.z)[0]")
}, },
}, },
} }

View file

@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"oss.terrastruct.com/util-go/go2"
"oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2format" "oss.terrastruct.com/d2/d2format"
) )
@ -12,7 +14,9 @@ import (
type Node interface { type Node interface {
node() node()
ast() d2ast.Node ast() d2ast.Node
Copy(newp Parent) Node Parent() Node
Copy(newp Node) Node
fmt.Stringer fmt.Stringer
} }
@ -22,16 +26,6 @@ var _ Node = &Edge{}
var _ Node = &Array{} var _ Node = &Array{}
var _ Node = &Map{} var _ Node = &Map{}
type Parent interface {
Node
Parent() Parent
}
var _ Parent = &Field{}
var _ Parent = &Edge{}
var _ Parent = &Array{}
var _ Parent = &Map{}
type Value interface { type Value interface {
Node Node
value() value()
@ -56,11 +50,11 @@ func (n *Edge) node() {}
func (n *Array) node() {} func (n *Array) node() {}
func (n *Map) node() {} func (n *Map) node() {}
func (n *Scalar) Parent() Parent { return n.parent } func (n *Scalar) Parent() Node { return n.parent }
func (n *Field) Parent() Parent { return n.parent } func (n *Field) Parent() Node { return n.parent }
func (n *Edge) Parent() Parent { return n.parent } func (n *Edge) Parent() Node { return n.parent }
func (n *Array) Parent() Parent { return n.parent } func (n *Array) Parent() Node { return n.parent }
func (n *Map) Parent() Parent { return n.parent } func (n *Map) Parent() Node { return n.parent }
func (n *Scalar) value() {} func (n *Scalar) value() {}
func (n *Array) value() {} func (n *Array) value() {}
@ -76,11 +70,11 @@ func (n *Array) String() string { return d2format.Format(n.ast()) }
func (n *Map) String() string { return d2format.Format(n.ast()) } func (n *Map) String() string { return d2format.Format(n.ast()) }
type Scalar struct { type Scalar struct {
parent Parent parent Node
Value d2ast.Scalar `json:"value"` Value d2ast.Scalar `json:"value"`
} }
func (s *Scalar) Copy(newp Parent) Node { func (s *Scalar) Copy(newp Node) Node {
tmp := *s tmp := *s
s = &tmp s = &tmp
@ -99,12 +93,12 @@ func (s *Scalar) Equal(s2 *Scalar) bool {
} }
type Map struct { type Map struct {
parent Parent parent Node
Fields []*Field `json:"fields"` Fields []*Field `json:"fields"`
Edges []*Edge `json:"edges"` Edges []*Edge `json:"edges"`
} }
func (m *Map) Copy(newp Parent) Node { func (m *Map) Copy(newp Node) Node {
tmp := *m tmp := *m
m = &tmp m = &tmp
@ -134,15 +128,15 @@ type Field struct {
Primary *Scalar `json:"primary,omitempty"` Primary *Scalar `json:"primary,omitempty"`
Composite Composite `json:"composite,omitempty"` Composite Composite `json:"composite,omitempty"`
References []KeyReference `json:"references,omitempty"` References []FieldReference `json:"references,omitempty"`
} }
func (f *Field) Copy(newp Parent) Node { func (f *Field) Copy(newp Node) Node {
tmp := *f tmp := *f
f = &tmp f = &tmp
f.parent = newp.(*Map) f.parent = newp.(*Map)
f.References = append([]KeyReference(nil), f.References...) f.References = append([]FieldReference(nil), f.References...)
if f.Primary != nil { if f.Primary != nil {
f.Primary = f.Primary.Copy(f).(*Scalar) f.Primary = f.Primary.Copy(f).(*Scalar)
} }
@ -221,6 +215,30 @@ func (eid *EdgeID) Match(eid2 *EdgeID) bool {
return true return true
} }
func (eid *EdgeID) resolveUnderscores(m *Map) (*EdgeID, *Map, error) {
eid = eid.Copy()
maxUnderscores := go2.Max(countUnderscores(eid.SrcPath), countUnderscores(eid.DstPath))
for i := 0; i < maxUnderscores; i++ {
if eid.SrcPath[0] == "_" {
eid.SrcPath = eid.SrcPath[1:]
} else {
mf := parentField(m)
eid.SrcPath = append([]string{mf.Name}, eid.SrcPath...)
}
if eid.DstPath[0] == "_" {
eid.DstPath = eid.DstPath[1:]
} else {
mf := parentField(m)
eid.DstPath = append([]string{mf.Name}, eid.DstPath...)
}
m = parentMap(m)
if m == nil {
return nil, nil, errors.New("invalid underscore")
}
}
return eid, m, nil
}
func (eid *EdgeID) trimCommon() (common []string, _ *EdgeID) { func (eid *EdgeID) trimCommon() (common []string, _ *EdgeID) {
eid = eid.Copy() eid = eid.Copy()
for len(eid.SrcPath) > 1 && len(eid.DstPath) > 1 { for len(eid.SrcPath) > 1 && len(eid.DstPath) > 1 {
@ -245,7 +263,7 @@ type Edge struct {
References []EdgeReference `json:"references,omitempty"` References []EdgeReference `json:"references,omitempty"`
} }
func (e *Edge) Copy(newp Parent) Node { func (e *Edge) Copy(newp Node) Node {
tmp := *e tmp := *e
e = &tmp e = &tmp
@ -261,11 +279,11 @@ func (e *Edge) Copy(newp Parent) Node {
} }
type Array struct { type Array struct {
parent Parent parent Node
Values []Value `json:"values"` Values []Value `json:"values"`
} }
func (a *Array) Copy(newp Parent) Node { func (a *Array) Copy(newp Node) Node {
tmp := *a tmp := *a
a = &tmp a = &tmp
@ -277,14 +295,14 @@ func (a *Array) Copy(newp Parent) Node {
return a return a
} }
type KeyReference struct { type FieldReference struct {
String *d2ast.StringBox `json:"string"` String *d2ast.StringBox `json:"string"`
KeyPath *d2ast.KeyPath `json:"key_path"` KeyPath *d2ast.KeyPath `json:"key_path"`
Context *RefContext `json:"-"` Context *RefContext `json:"-"`
} }
func (kr KeyReference) KeyPathIndex() int { func (kr FieldReference) KeyPathIndex() int {
for i, sb := range kr.KeyPath.Path { for i, sb := range kr.KeyPath.Path {
if sb == kr.String { if sb == kr.String {
return i return i
@ -293,11 +311,11 @@ func (kr KeyReference) KeyPathIndex() int {
panic("d2ir.KeyReference.KeyPathIndex: String not in KeyPath?") panic("d2ir.KeyReference.KeyPathIndex: String not in KeyPath?")
} }
func (kr KeyReference) EdgeDest() bool { func (kr FieldReference) EdgeDest() bool {
return kr.KeyPath == kr.Context.Edge.Dst return kr.KeyPath == kr.Context.Edge.Dst
} }
func (kr KeyReference) InEdge() bool { func (kr FieldReference) InEdge() bool {
return kr.KeyPath != kr.Context.Key.Key return kr.KeyPath != kr.Context.Key.Key
} }
@ -311,7 +329,6 @@ type RefContext struct {
Scope *d2ast.Map Scope *d2ast.Map
// UnresolvedScopeMap is prior to interpreting _ // UnresolvedScopeMap is prior to interpreting _
ScopeMap *Map
UnresolvedScopeMap *Map UnresolvedScopeMap *Map
} }
@ -347,6 +364,11 @@ func (m *Map) EdgeCountRecursive() int {
return 0 return 0
} }
acc := len(m.Edges) acc := len(m.Edges)
for _, f := range m.Fields {
if f_m, ok := f.Composite.(*Map); ok {
acc += f_m.EdgeCountRecursive()
}
}
for _, e := range m.Edges { for _, e := range m.Edges {
if e.Map != nil { if e.Map != nil {
acc += e.Map.EdgeCountRecursive() acc += e.Map.EdgeCountRecursive()
@ -355,7 +377,17 @@ func (m *Map) EdgeCountRecursive() int {
return acc return acc
} }
func (m *Map) Get(ida []string) *Field { func (m *Map) GetField(ida []string) *Field {
for len(ida) > 0 && ida[0] == "_" {
m = parentMap(m)
if m == nil {
return nil
}
}
return m.getField(ida)
}
func (m *Map) getField(ida []string) *Field {
if len(ida) == 0 { if len(ida) == 0 {
return nil return nil
} }
@ -363,6 +395,10 @@ func (m *Map) Get(ida []string) *Field {
s := ida[0] s := ida[0]
rest := ida[1:] rest := ida[1:]
if s == "_" {
return nil
}
for _, f := range m.Fields { for _, f := range m.Fields {
if !strings.EqualFold(f.Name, s) { if !strings.EqualFold(f.Name, s) {
continue continue
@ -371,20 +407,35 @@ func (m *Map) Get(ida []string) *Field {
return f return f
} }
if f_m, ok := f.Composite.(*Map); ok { if f_m, ok := f.Composite.(*Map); ok {
return f_m.Get(rest) return f_m.getField(rest)
} }
} }
return nil return nil
} }
func (m *Map) Ensure(ida []string) (*Field, error) { func (m *Map) EnsureField(ida []string) (*Field, error) {
for len(ida) > 0 && ida[0] == "_" {
m = parentMap(m)
if m == nil {
return nil, errors.New("invalid underscore")
}
ida = ida[1:]
}
return m.ensureField(ida)
}
func (m *Map) ensureField(ida []string) (*Field, error) {
if len(ida) == 0 { if len(ida) == 0 {
return nil, errors.New("empty ida") return nil, errors.New("invalid underscore")
} }
s := ida[0] s := ida[0]
rest := ida[1:] rest := ida[1:]
if s == "_" {
return nil, errors.New(`parent "_" can only be used in the beginning of paths, e.g. "_.x"`)
}
for _, f := range m.Fields { for _, f := range m.Fields {
if !strings.EqualFold(f.Name, s) { if !strings.EqualFold(f.Name, s) {
continue continue
@ -394,14 +445,14 @@ func (m *Map) Ensure(ida []string) (*Field, error) {
} }
switch fc := f.Composite.(type) { switch fc := f.Composite.(type) {
case *Map: case *Map:
return fc.Ensure(rest) return fc.ensureField(rest)
case *Array: case *Array:
return nil, errors.New("cannot index into array") return nil, errors.New("cannot index into array")
} }
f.Composite = &Map{ f.Composite = &Map{
parent: f, parent: f,
} }
return f.Composite.(*Map).Ensure(rest) return f.Composite.(*Map).ensureField(rest)
} }
f := &Field{ f := &Field{
@ -415,7 +466,7 @@ func (m *Map) Ensure(ida []string) (*Field, error) {
f.Composite = &Map{ f.Composite = &Map{
parent: f, parent: f,
} }
return f.Composite.(*Map).Ensure(rest) return f.Composite.(*Map).ensureField(rest)
} }
func (m *Map) Delete(ida []string) bool { func (m *Map) Delete(ida []string) bool {
@ -442,9 +493,13 @@ func (m *Map) Delete(ida []string) bool {
} }
func (m *Map) GetEdges(eid *EdgeID) []*Edge { func (m *Map) GetEdges(eid *EdgeID) []*Edge {
eid, m, err := eid.resolveUnderscores(m)
if err != nil {
return nil
}
common, eid := eid.trimCommon() common, eid := eid.trimCommon()
if len(common) > 0 { if len(common) > 0 {
f := m.Get(common) f := m.GetField(common)
if f == nil { if f == nil {
return nil return nil
} }
@ -464,9 +519,13 @@ func (m *Map) GetEdges(eid *EdgeID) []*Edge {
} }
func (m *Map) EnsureEdge(eid *EdgeID) (*Edge, error) { func (m *Map) EnsureEdge(eid *EdgeID) (*Edge, error) {
eid, m, err := eid.resolveUnderscores(m)
if err != nil {
return nil, err
}
common, eid := eid.trimCommon() common, eid := eid.trimCommon()
if len(common) > 0 { if len(common) > 0 {
f, err := m.Ensure(common) f, err := m.EnsureField(common)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -560,8 +619,10 @@ func (m *Map) ast() d2ast.Node {
return nil return nil
} }
astMap := &d2ast.Map{} astMap := &d2ast.Map{}
if m.parent != nil { if m.parent == nil {
astMap.Range = d2ast.MakeRange(",1:0:0-1:0:0") astMap.Range = d2ast.MakeRange(",0:0:0-1:0:0")
} else {
astMap.Range = d2ast.MakeRange(",1:0:0-2:0:0")
} }
for _, f := range m.Fields { for _, f := range m.Fields {
astMap.Nodes = append(astMap.Nodes, d2ast.MakeMapNodeBox(f.ast().(d2ast.MapNode))) astMap.Nodes = append(astMap.Nodes, d2ast.MakeMapNodeBox(f.ast().(d2ast.MapNode)))
@ -572,14 +633,14 @@ func (m *Map) ast() d2ast.Node {
return astMap return astMap
} }
func (m *Map) appendKeyReferences(i int, kp *d2ast.KeyPath, refctx *RefContext) { func (m *Map) appendFieldReferences(i int, kp *d2ast.KeyPath, refctx *RefContext) {
sb := kp.Path[i] sb := kp.Path[i]
f := m.Get([]string{sb.Unbox().ScalarString()}) f := m.GetField([]string{sb.Unbox().ScalarString()})
if f == nil { if f == nil {
return return
} }
f.References = append(f.References, KeyReference{ f.References = append(f.References, FieldReference{
String: sb, String: sb,
KeyPath: kp, KeyPath: kp,
Context: refctx, Context: refctx,
@ -588,7 +649,7 @@ func (m *Map) appendKeyReferences(i int, kp *d2ast.KeyPath, refctx *RefContext)
return return
} }
if f_m, ok := f.Composite.(*Map); ok { if f_m, ok := f.Composite.(*Map); ok {
f_m.appendKeyReferences(i+1, kp, refctx) f_m.appendFieldReferences(i+1, kp, refctx)
} }
} }
@ -596,6 +657,37 @@ func (m *Map) appendEdgeReferences(e *Edge, refctx *RefContext) {
e.References = append(e.References, EdgeReference{ e.References = append(e.References, EdgeReference{
Context: refctx, Context: refctx,
}) })
m.appendKeyReferences(0, refctx.Edge.Src, refctx) m.appendFieldReferences(0, refctx.Edge.Src, refctx)
m.appendKeyReferences(0, refctx.Edge.Dst, refctx) m.appendFieldReferences(0, refctx.Edge.Dst, refctx)
}
func parentMap(n Node) *Map {
for n.Parent() != nil {
n = n.Parent()
if n_m, ok := n.(*Map); ok {
return n_m
}
}
return nil
}
func parentField(n Node) *Field {
for n.Parent() != nil {
n = n.Parent()
if n_f, ok := n.(*Field); ok {
return n_f
}
}
return nil
}
func countUnderscores(p []string) int {
var count int
for _, el := range p {
if el != "_" {
break
}
count++
}
return count
} }

25
testdata/d2ir/TestCompile/edge/root.exp.json generated vendored Normal file
View file

@ -0,0 +1,25 @@
{
"fields": [
{
"name": "x"
},
{
"name": "y"
}
],
"edges": [
{
"edge_id": {
"src_path": [
"x"
],
"src_arrow": false,
"dst_path": [
"y"
],
"dst_arrow": true,
"index": 0
}
}
]
}

View file

@ -1,29 +1,29 @@
{ {
"fields": [ "fields": [
{ {
"name": "x", "name": "p",
"composite": { "composite": {
"fields": [ "fields": [
{ {
"name": "_" "name": "z"
} }
], ],
"edges": null "edges": null
} }
}, },
{ {
"name": "z" "name": "x"
} }
], ],
"edges": [ "edges": [
{ {
"edge_id": { "edge_id": {
"src_path": [ "src_path": [
"x", "x"
"_"
], ],
"src_arrow": false, "src_arrow": false,
"dst_path": [ "dst_path": [
"p",
"z" "z"
], ],
"dst_arrow": true, "dst_arrow": true,