d2ir: Import and cleanup

Test files still need cleanup.
This commit is contained in:
Anmol Sethi 2023-01-09 13:26:11 -08:00
parent ef0e197a63
commit a277d10dda
No known key found for this signature in database
GPG key ID: 25BC68888A99A8BA
4 changed files with 719 additions and 0 deletions

57
d2ir/apply.go Normal file
View file

@ -0,0 +1,57 @@
package d2ir
import (
"fmt"
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2graph"
"oss.terrastruct.com/d2/d2parser"
)
type compiler struct {
err d2parser.ParseError
}
func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) {
f = "%v: " + f
v = append([]interface{}{n.GetRange()}, v...)
c.err.Errors = append(c.err.Errors, d2ast.Error{
Range: n.GetRange(),
Message: fmt.Sprintf(f, v...),
})
}
func Apply(dst *Map, ast *d2ast.Map) error {
var c compiler
c.apply(dst, ast)
return c.err
}
func (c *compiler) apply(dst *Map, ast *d2ast.Map) {
for _, n := range ast.Nodes {
if n.MapKey == nil {
continue
}
c.applyKey(dst, n.MapKey)
}
}
func (c *compiler) applyKey(dst *Map, k *d2ast.Key) {
if k.Key != nil && len(k.Key.Path) > 0 {
f, ok := dst.Ensure(d2graph.Key(k.Key))
if !ok {
c.errorf(k.Key, "cannot index into array")
return
}
if len(k.Edges) == 0 {
if k.Primary.Unbox() != nil {
f.Primary = &Scalar{
parent: f,
Value: k.Primary.Unbox(),
}
}
}
}
}

199
d2ir/apply_test.go Normal file
View file

@ -0,0 +1,199 @@
package d2ir_test
import (
"fmt"
"math/big"
"path/filepath"
"strings"
"testing"
"oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2ir"
"oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/internal/assert"
)
type testCase struct {
name string
text string
base *d2ir.Map
exp func(testing.TB, *d2ir.Map, error)
}
func TestApply(t *testing.T) {
t.Parallel()
t.Run("simple", testApplySimple)
}
func testApplySimple(t *testing.T) {
tcs := []testCase{
{
name: "one",
text: `x`,
exp: func(t testing.TB, m *d2ir.Map, err error) {
assert.Success(t, err)
assertField(t, m, 1, 0, nil)
assertField(t, m, 0, 0, nil, "x")
},
},
{
name: "nested",
text: `x.y -> z.p`,
exp: func(t testing.TB, m *d2ir.Map, err error) {
assert.Success(t, err)
assertField(t, m, 4, 1, nil)
assertField(t, m, 1, 0, nil, "x")
assertField(t, m, 0, 0, nil, "x", "y")
assertField(t, m, 1, 0, nil, "z")
assertField(t, m, 0, 0, nil, "z", "p")
assertEdge(t, m, 0, nil, &d2ir.EdgeID{
[]string{"x", "y"}, false,
[]string{"z", "p"}, true,
-1,
})
},
},
{
name: "underscore_parent",
text: `x._ -> z`,
exp: func(t testing.TB, m *d2ir.Map, err error) {
assert.Success(t, err)
assertField(t, m, 2, 1, nil)
assertField(t, m, 0, 0, nil, "x")
assertField(t, m, 0, 0, nil, "z")
assertEdge(t, m, 0, nil, &d2ir.EdgeID{
[]string{"x"}, false,
[]string{"z"}, true,
-1,
})
},
},
}
runa(t, tcs)
}
func runa(t *testing.T, tcs []testCase) {
for _, tc := range tcs {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
run(t, tc)
})
}
}
func run(t testing.TB, tc testCase) {
d2Path := fmt.Sprintf("d2/testdata/d2ir/%v.d2", t.Name())
ast, err := d2parser.Parse(d2Path, strings.NewReader(tc.text), nil)
if err != nil {
tc.exp(t, nil, err)
t.FailNow()
return
}
dst := tc.base.Copy(nil).(*d2ir.Map)
err = d2ir.Apply(dst, ast)
tc.exp(t, dst, err)
err = diff.Testdata(filepath.Join("..", "testdata", "d2ir", t.Name()), dst)
if err != nil {
tc.exp(t, nil, err)
t.FailNow()
return
}
}
func assertField(t testing.TB, n d2ir.Node, nfields, nedges int, primary interface{}, ida ...string) *d2ir.Field {
t.Helper()
m := d2ir.NodeToMap(n)
p := d2ir.NodeToPrimary(n)
var f *d2ir.Field
if len(ida) > 0 {
f = m.Get(ida)
if f == nil {
t.Fatalf("expected field %#v in map %#v but not found", ida, m)
}
p = f.Primary
m = d2ir.NodeToMap(f)
}
if m.FieldCount() != nfields {
t.Fatalf("expected %d fields but got %d", nfields, m.FieldCount())
}
if m.EdgeCount() != nedges {
t.Fatalf("expected %d edges but got %d", nedges, m.EdgeCount())
}
if !p.Equal(makeScalar(primary)) {
t.Fatalf("expected primary %#v but %#v", primary, p)
}
return f
}
func assertEdge(t testing.TB, n d2ir.Node, nfields int, primary interface{}, eid *d2ir.EdgeID) *d2ir.Edge {
t.Helper()
m := d2ir.NodeToMap(n)
e := m.GetEdge(eid)
if e == nil {
t.Fatalf("expected edge %#v in map %#v but not found", eid, m)
}
if e.Map.FieldCount() != nfields {
t.Fatalf("expected %d fields but got %d", nfields, e.Map.FieldCount())
}
if e.Map.EdgeCount() != 0 {
t.Fatalf("expected %d edges but got %d", 0, e.Map.EdgeCount())
}
if !e.Primary.Equal(makeScalar(primary)) {
t.Fatalf("expected primary %#v but %#v", primary, e.Primary)
}
return e
}
func makeScalar(v interface{}) *d2ir.Scalar {
s := &d2ir.Scalar{}
switch v := v.(type) {
case bool:
s.Value = &d2ast.Boolean{
Value: v,
}
case float64:
bv := &big.Rat{}
bv.SetFloat64(v)
s.Value = &d2ast.Number{
Value: bv,
}
case int:
s.Value = &d2ast.Number{
Value: big.NewRat(int64(v), 1),
}
case string:
s.Value = d2ast.FlatDoubleQuotedString(v)
default:
if v != nil {
panic(fmt.Sprintf("d2ir: unexpected type to makeScalar: %#v", v))
}
s.Value = &d2ast.Null{}
}
return s
}

386
d2ir/d2ir.go Normal file
View file

@ -0,0 +1,386 @@
package d2ir
import (
"strings"
"oss.terrastruct.com/d2/d2ast"
)
type Node interface {
node()
Copy(newp Parent) Node
}
var _ Node = &Scalar{}
var _ Node = &Field{}
var _ Node = &Edge{}
var _ Node = &Array{}
var _ Node = &Map{}
type Parent interface {
Node
Parent() Parent
}
var _ Parent = &Field{}
var _ Parent = &Edge{}
var _ Parent = &Array{}
var _ Parent = &Map{}
type Value interface {
Node
value()
}
var _ Value = &Scalar{}
var _ Value = &Array{}
var _ Value = &Map{}
type Composite interface {
Node
Value
composite()
}
var _ Composite = &Array{}
var _ Composite = &Map{}
func (n *Scalar) node() {}
func (n *Field) node() {}
func (n *Edge) node() {}
func (n *Array) node() {}
func (n *Map) node() {}
func (n *Field) Parent() Parent { return n.parent }
func (n *Edge) Parent() Parent { return n.parent }
func (n *Array) Parent() Parent { return n.parent }
func (n *Map) Parent() Parent { return n.parent }
func (n *Scalar) value() {}
func (n *Array) value() {}
func (n *Map) value() {}
func (n *Array) composite() {}
func (n *Map) composite() {}
type Scalar struct {
parent Parent
Value d2ast.Scalar `json:"value"`
}
func (s *Scalar) Copy(newp Parent) Node {
tmp := *s
s = &tmp
s.parent = newp
return s
}
func (s *Scalar) Equal(s2 *Scalar) bool {
return s.Value.ScalarString() == s2.Value.ScalarString() && s.Value.Type() == s2.Value.Type()
}
type Map struct {
parent Parent
Fields []*Field `json:"fields"`
Edges []*Edge `json:"edges"`
}
func (m *Map) Copy(newp Parent) Node {
tmp := *m
m = &tmp
m.parent = newp
m.Fields = append([]*Field(nil), m.Fields...)
for i := range m.Fields {
m.Fields[i] = m.Fields[i].Copy(m).(*Field)
}
m.Edges = append([]*Edge(nil), m.Edges...)
for i := range m.Edges {
m.Edges[i] = m.Edges[i].Copy(m).(*Edge)
}
return m
}
// Root reports whether the Map is the root of the D2 tree.
// The root map has no parent.
func (m *Map) Root() bool {
return m.parent == nil
}
type Field struct {
parent *Map
Name string `json:"name"`
Primary *Scalar `json:"primary"`
Composite Composite `json:"composite"`
Refs []KeyReference `json:"refs"`
}
func (f *Field) Copy(newp Parent) Node {
tmp := *f
f = &tmp
f.parent = newp.(*Map)
f.Refs = append([]KeyReference(nil), f.Refs...)
if f.Primary != nil {
f.Primary = f.Primary.Copy(f).(*Scalar)
}
if f.Composite != nil {
f.Composite = f.Composite.Copy(f).(Composite)
}
return f
}
type EdgeID struct {
SrcPath []string `json:"src_path"`
SrcArrow bool `json:"src_arrow"`
DstPath []string `json:"dst_path"`
DstArrow bool `json:"dst_arrow"`
Index int `json:"index"`
}
func (eid *EdgeID) Copy() *EdgeID {
tmp := *eid
eid = &tmp
eid.SrcPath = append([]string(nil), eid.SrcPath...)
eid.DstPath = append([]string(nil), eid.DstPath...)
return eid
}
func (eid *EdgeID) Equal(eid2 *EdgeID) bool {
if eid.Index != eid2.Index {
return false
}
if len(eid.SrcPath) != len(eid2.SrcPath) {
return false
}
if eid.SrcArrow != eid2.SrcArrow {
return false
}
for i, s := range eid.SrcPath {
if !strings.EqualFold(s, eid2.SrcPath[i]) {
return false
}
}
if len(eid.DstPath) != len(eid2.DstPath) {
return false
}
if eid.DstArrow != eid2.DstArrow {
return false
}
for i, s := range eid.DstPath {
if !strings.EqualFold(s, eid2.DstPath[i]) {
return false
}
}
return true
}
func (eid *EdgeID) trimCommon() (common []string, _ *EdgeID) {
eid = eid.Copy()
for len(eid.SrcPath) > 1 && len(eid.DstPath) > 1 {
if !strings.EqualFold(eid.SrcPath[0], eid.DstPath[0]) {
return common, eid
}
common = append(common, eid.SrcPath[0])
eid.SrcPath = eid.SrcPath[1:]
eid.DstPath = eid.DstPath[1:]
}
return common, eid
}
type Edge struct {
parent *Map
ID *EdgeID `json:"edge_id"`
Primary *Scalar `json:"primary"`
Map *Map `json:"map"`
Refs []EdgeReference `json:"refs"`
}
func (e *Edge) Copy(newp Parent) Node {
tmp := *e
e = &tmp
e.parent = newp.(*Map)
e.Refs = append([]EdgeReference(nil), e.Refs...)
if e.Primary != nil {
e.Primary = e.Primary.Copy(e).(*Scalar)
}
if e.Map != nil {
e.Map = e.Map.Copy(e).(*Map)
}
return e
}
type Array struct {
parent Parent
Values []Value `json:"values"`
}
func (a *Array) Copy(newp Parent) Node {
tmp := *a
a = &tmp
a.parent = newp
a.Values = append([]Value(nil), a.Values...)
for i := range a.Values {
a.Values[i] = a.Values[i].Copy(a).(Value)
}
return a
}
type KeyReference struct {
String *d2ast.StringBox `json:"string"`
KeyPath *d2ast.KeyPath `json:"key_path"`
RefCtx *RefContext `json:"ref_ctx"`
}
type EdgeReference struct {
RefCtx *RefContext `json:"ref_ctx"`
}
type RefContext struct {
Key *d2ast.Key `json:"-"`
Edge *d2ast.Edge `json:"-"`
Scope *d2ast.Map `json:"-"`
}
func (m *Map) FieldCount() int {
acc := len(m.Fields)
for _, f := range m.Fields {
if f_m, ok := f.Composite.(*Map); ok {
acc += f_m.FieldCount()
}
}
return acc
}
func (m *Map) EdgeCount() int {
acc := len(m.Edges)
for _, e := range m.Edges {
if e.Map != nil {
acc += e.Map.EdgeCount()
}
}
return acc
}
func (m *Map) Get(ida []string) (*Field, bool) {
if len(ida) == 0 {
return nil, false
}
s := ida[0]
rest := ida[1:]
for _, f := range m.Fields {
if !strings.EqualFold(f.Name, s) {
continue
}
if len(rest) == 0 {
return f, true
}
if f_m, ok := f.Composite.(*Map); ok {
return f_m.Get(rest)
}
}
return nil, false
}
func (m *Map) Ensure(ida []string) (*Field, bool) {
if len(ida) == 0 {
return nil, false
}
s := ida[0]
rest := ida[1:]
for _, f := range m.Fields {
if !strings.EqualFold(f.Name, s) {
continue
}
if len(rest) == 0 {
return f, true
}
switch fc := f.Composite.(type) {
case *Map:
return fc.Ensure(rest)
case *Array:
return nil, false
}
f.Composite = &Map{
parent: f,
}
return f.Composite.(*Map).Ensure(rest)
}
f := &Field{
parent: m,
Name: s,
}
m.Fields = append(m.Fields, f)
if len(rest) == 0 {
return f, true
}
f.Composite = &Map{
parent: f,
}
return f.Composite.(*Map).Ensure(rest)
}
func (m *Map) Delete(ida []string) bool {
if len(ida) == 0 {
return false
}
s := ida[0]
rest := ida[1:]
for i, f := range m.Fields {
if !strings.EqualFold(f.Name, s) {
continue
}
if len(rest) == 0 {
copy(m.Fields[i:], m.Fields[i+1:])
return true
}
if f_m, ok := f.Composite.(*Map); ok {
return f_m.Delete(rest)
}
}
return false
}
func (m *Map) GetEdge(eid *EdgeID) (*Edge, bool) {
common, eid := eid.trimCommon()
if len(common) > 0 {
f, ok := m.Get(common)
if !ok {
return nil, false
}
if f_m, ok := f.Composite.(*Map); ok {
return f_m.GetEdge(eid)
}
return nil, false
}
for _, e := range m.Edges {
if e.ID.Equal(eid) {
return e, true
}
}
return nil, false
}

77
d2ir/d2ir_test.go Normal file
View file

@ -0,0 +1,77 @@
package d2ir_test
import (
"testing"
"oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2ir"
"oss.terrastruct.com/d2/internal/assert"
)
func TestCopy(t *testing.T) {
t.Parallel()
const scalStr = `Those who claim the dead never return to life haven't ever been around.`
s := &d2ir.Scalar{
parent: nil,
Value: d2ast.FlatUnquotedString(scalStr),
}
a := &d2ir.Array{
Parent: nil,
Values: []d2ir.Value{
&d2ir.Scalar{
parent: nil,
Value: &d2ast.Boolean{
Value: true,
},
},
},
}
m2 := &d2ir.Map{
Parent: nil,
Fields: []*d2ir.Field{
{Primary: s},
},
}
const keyStr = `Absence makes the heart grow frantic.`
f := &d2ir.Field{
Parent: nil,
Name: keyStr,
Primary: s,
Composite: a,
}
e := &d2ir.Edge{
Parent: nil,
Primary: s,
Map: m2,
}
m := &d2ir.Map{
Parent: nil,
Fields: []*d2ir.Field{f},
Edges: []*d2ir.Edge{e},
}
m = m.Copy(nil).(*d2ir.Map)
f.Name = `Many a wife thinks her husband is the world's greatest lover.`
assert.Equal(t, m, m.Fields[0].Parent)
assert.Equal(t, keyStr, m.Fields[0].Name)
assert.Equal(t, m.Fields[0], m.Fields[0].Primary.parent)
assert.Equal(t, m.Fields[0], m.Fields[0].Composite.(*d2ir.Array).Parent)
assert.Equal(t,
m.Fields[0].Composite,
m.Fields[0].Composite.(*d2ir.Array).Values[0].(*d2ir.Scalar).parent,
)
assert.Equal(t, m, m.Edges[0].Parent)
assert.Equal(t, m.Edges[0], m.Edges[0].Primary.parent)
assert.Equal(t, m.Edges[0], m.Edges[0].Map.Parent)
assert.Equal(t, m.Edges[0].Map, m.Edges[0].Map.Fields[0].Parent)
assert.Equal(t, m.Edges[0].Map.Fields[0], m.Edges[0].Map.Fields[0].Primary.parent)
}