d2ir: wip

This commit is contained in:
Anmol Sethi 2023-01-11 11:05:50 -08:00
parent 4b50748dd0
commit 83ef53dc40
No known key found for this signature in database
GPG key ID: 25BC68888A99A8BA
8 changed files with 139 additions and 87 deletions

View file

@ -24,8 +24,11 @@ func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) {
func Apply(dst *Map, ast *d2ast.Map) error { func Apply(dst *Map, ast *d2ast.Map) error {
var c compiler var c compiler
c.apply(dst, ast) c.apply(dst, ast)
if !c.err.Empty() {
return c.err return c.err
} }
return nil
}
func (c *compiler) apply(dst *Map, ast *d2ast.Map) { func (c *compiler) apply(dst *Map, ast *d2ast.Map) {
for _, n := range ast.Nodes { for _, n := range ast.Nodes {

View file

@ -7,20 +7,17 @@ import (
"strings" "strings"
"testing" "testing"
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/util-go/diff" "oss.terrastruct.com/util-go/diff"
"oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2ir" "oss.terrastruct.com/d2/d2ir"
"oss.terrastruct.com/d2/d2parser" "oss.terrastruct.com/d2/d2parser"
"oss.terrastruct.com/d2/internal/assert"
) )
type testCase struct { type testCase struct {
name string name string
text string run func(testing.TB, *d2ir.Map)
base *d2ir.Map
exp func(testing.TB, *d2ir.Map, error)
} }
func TestApply(t *testing.T) { func TestApply(t *testing.T) {
@ -30,12 +27,13 @@ func TestApply(t *testing.T) {
} }
func testApplySimple(t *testing.T) { func testApplySimple(t *testing.T) {
tcs := []testCase{ t.Parallel()
tca := []testCase{
{ {
name: "one", name: "one",
text: `x`, run: func(t testing.TB, m *d2ir.Map) {
err := parse(t, m, `x`)
exp: func(t testing.TB, m *d2ir.Map, err error) {
assert.Success(t, err) assert.Success(t, err)
assertField(t, m, 1, 0, nil) assertField(t, m, 1, 0, nil)
@ -44,9 +42,8 @@ func testApplySimple(t *testing.T) {
}, },
{ {
name: "nested", name: "nested",
text: `x.y -> z.p`, run: func(t testing.TB, m *d2ir.Map) {
err := parse(t, m, `x.y -> z.p`)
exp: func(t testing.TB, m *d2ir.Map, err error) {
assert.Success(t, err) assert.Success(t, err)
assertField(t, m, 4, 1, nil) assertField(t, m, 4, 1, nil)
@ -65,9 +62,8 @@ func testApplySimple(t *testing.T) {
}, },
{ {
name: "underscore_parent", name: "underscore_parent",
text: `x._ -> z`, run: func(t testing.TB, m *d2ir.Map) {
err := parse(t, m, `x._ -> z`)
exp: func(t testing.TB, m *d2ir.Map, err error) {
assert.Success(t, err) assert.Success(t, err)
assertField(t, m, 2, 1, nil) assertField(t, m, 2, 1, nil)
@ -83,63 +79,76 @@ func testApplySimple(t *testing.T) {
}, },
} }
runa(t, tcs) runa(t, tca)
} }
func runa(t *testing.T, tcs []testCase) { func runa(t *testing.T, tca []testCase) {
for _, tc := range tcs { for _, tc := range tca {
tc := tc tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Parallel() t.Parallel()
m := &d2ir.Map{}
run(t, tc) tc.run(t, m)
}) })
} }
} }
func run(t testing.TB, tc testCase) { func parse(t testing.TB, dst *d2ir.Map, text string) error {
t.Helper()
d2Path := fmt.Sprintf("d2/testdata/d2ir/%v.d2", t.Name()) d2Path := fmt.Sprintf("d2/testdata/d2ir/%v.d2", t.Name())
ast, err := d2parser.Parse(d2Path, strings.NewReader(tc.text), nil) ast, err := d2parser.Parse(d2Path, strings.NewReader(text), nil)
if err != nil { assert.Success(t, err)
tc.exp(t, nil, err)
t.FailNow()
return
}
dst := tc.base.Copy(nil).(*d2ir.Map)
err = d2ir.Apply(dst, ast) err = d2ir.Apply(dst, ast)
tc.exp(t, dst, err)
err = diff.Testdata(filepath.Join("..", "testdata", "d2ir", t.Name()), dst)
if err != nil { if err != nil {
tc.exp(t, nil, err) return err
t.FailNow()
return
} }
err = diff.TestdataJSON(filepath.Join("..", "testdata", "d2ir", t.Name()), dst)
return err
} }
func assertField(t testing.TB, n d2ir.Node, nfields, nedges int, primary interface{}, ida ...string) *d2ir.Field { func assertField(t testing.TB, n d2ir.Node, nfields, nedges int, primary interface{}, ida ...string) *d2ir.Field {
t.Helper() t.Helper()
m := d2ir.NodeToMap(n) var m *d2ir.Map
p := d2ir.NodeToPrimary(n) p := &d2ir.Scalar{
Value: &d2ast.Null{},
}
switch n := n.(type) {
case *d2ir.Field:
mm, ok := n.Composite.(*d2ir.Map)
if ok {
m = mm
} else {
t.Fatalf("unexpected d2ir.Field.Composite %T", n.Composite)
}
p = n.Primary
case *d2ir.Map:
m = n
p.Value = &d2ast.Null{}
default:
t.Fatalf("unexpected d2ir.Node %T", n)
}
var f *d2ir.Field var f *d2ir.Field
var ok bool
if len(ida) > 0 { if len(ida) > 0 {
f = m.Get(ida) f, ok = m.Get(ida)
if f == nil { if !ok {
t.Fatalf("expected field %#v in map %#v but not found", ida, m) t.Fatalf("expected field %v in map %s", ida, m)
} }
p = f.Primary p = f.Primary
m = d2ir.NodeToMap(f) if f_m, ok := f.Composite.(*d2ir.Map); ok {
m = f_m
} else {
m = &d2ir.Map{}
}
} }
if m.FieldCount() != nfields { assert.Equal(t, nfields, m.FieldCount())
t.Fatalf("expected %d fields but got %d", nfields, m.FieldCount()) assert.Equal(t, nedges, m.EdgeCount())
}
if m.EdgeCount() != nedges {
t.Fatalf("expected %d edges but got %d", nedges, m.EdgeCount())
}
if !p.Equal(makeScalar(primary)) { if !p.Equal(makeScalar(primary)) {
t.Fatalf("expected primary %#v but %#v", primary, p) t.Fatalf("expected primary %#v but %#v", primary, p)
} }
@ -150,19 +159,27 @@ func assertField(t testing.TB, n d2ir.Node, nfields, nedges int, primary interfa
func assertEdge(t testing.TB, n d2ir.Node, nfields int, primary interface{}, eid *d2ir.EdgeID) *d2ir.Edge { func assertEdge(t testing.TB, n d2ir.Node, nfields int, primary interface{}, eid *d2ir.EdgeID) *d2ir.Edge {
t.Helper() t.Helper()
m := d2ir.NodeToMap(n) var m *d2ir.Map
switch n := n.(type) {
e := m.GetEdge(eid) case *d2ir.Field:
if e == nil { mm, ok := n.Composite.(*d2ir.Map)
t.Fatalf("expected edge %#v in map %#v but not found", eid, m) if ok {
m = mm
} else {
t.Fatalf("unexpected d2ir.Field.Composite %T", n.Composite)
}
case *d2ir.Map:
m = n
default:
t.Fatalf("unexpected d2ir.Node %T", n)
} }
if e.Map.FieldCount() != nfields { e, ok := m.GetEdge(eid)
t.Fatalf("expected %d fields but got %d", nfields, e.Map.FieldCount()) if !ok {
} t.Fatalf("expected edge %v in map %s but not found", eid, m)
if e.Map.EdgeCount() != 0 {
t.Fatalf("expected %d edges but got %d", 0, e.Map.EdgeCount())
} }
assert.Equal(t, nfields, e.Map.FieldCount())
if !e.Primary.Equal(makeScalar(primary)) { if !e.Primary.Equal(makeScalar(primary)) {
t.Fatalf("expected primary %#v but %#v", primary, e.Primary) t.Fatalf("expected primary %#v but %#v", primary, e.Primary)
} }

View file

@ -1,6 +1,8 @@
package d2ir package d2ir
import ( import (
"encoding/json"
"fmt"
"strings" "strings"
"oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2ast"
@ -51,6 +53,7 @@ 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 *Field) Parent() Parent { return n.parent } func (n *Field) Parent() Parent { return n.parent }
func (n *Edge) Parent() Parent { return n.parent } func (n *Edge) Parent() Parent { return n.parent }
func (n *Array) Parent() Parent { return n.parent } func (n *Array) Parent() Parent { return n.parent }
@ -77,6 +80,17 @@ func (s *Scalar) Copy(newp Parent) Node {
} }
func (s *Scalar) Equal(s2 *Scalar) bool { func (s *Scalar) Equal(s2 *Scalar) bool {
if s == nil {
if s2 == nil {
return true
}
_, ok := s2.Value.(*d2ast.Null)
return ok
}
if s2 == nil {
_, ok := s.Value.(*d2ast.Null)
return ok
}
return s.Value.ScalarString() == s2.Value.ScalarString() && s.Value.Type() == s2.Value.Type() return s.Value.ScalarString() == s2.Value.ScalarString() && s.Value.Type() == s2.Value.Type()
} }
@ -113,10 +127,10 @@ type Field struct {
Name string `json:"name"` Name string `json:"name"`
Primary *Scalar `json:"primary"` Primary *Scalar `json:"primary,omitempty"`
Composite Composite `json:"composite"` Composite Composite `json:"composite,omitempty"`
Refs []KeyReference `json:"refs"` Refs []KeyReference `json:"refs,omitempty"`
} }
func (f *Field) Copy(newp Parent) Node { func (f *Field) Copy(newp Parent) Node {
@ -203,10 +217,10 @@ type Edge struct {
ID *EdgeID `json:"edge_id"` ID *EdgeID `json:"edge_id"`
Primary *Scalar `json:"primary"` Primary *Scalar `json:"primary,omitempty"`
Map *Map `json:"map"` Map *Map `json:"map,omitempty"`
Refs []EdgeReference `json:"refs"` Refs []EdgeReference `json:"refs,omitempty"`
} }
func (e *Edge) Copy(newp Parent) Node { func (e *Edge) Copy(newp Parent) Node {
@ -384,3 +398,11 @@ func (m *Map) GetEdge(eid *EdgeID) (*Edge, bool) {
} }
return nil, false return nil, false
} }
func (m *Map) String() string {
b, err := json.Marshal(m)
if err != nil {
panic(fmt.Sprintf("d2ir: failed to marshal d2ir.Map: %v", err))
}
return string(b)
}

View file

@ -3,9 +3,10 @@ package d2ir_test
import ( import (
"testing" "testing"
"oss.terrastruct.com/util-go/assert"
"oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2ast"
"oss.terrastruct.com/d2/d2ir" "oss.terrastruct.com/d2/d2ir"
"oss.terrastruct.com/d2/internal/assert"
) )
func TestCopy(t *testing.T) { func TestCopy(t *testing.T) {
@ -13,14 +14,11 @@ func TestCopy(t *testing.T) {
const scalStr = `Those who claim the dead never return to life haven't ever been around.` const scalStr = `Those who claim the dead never return to life haven't ever been around.`
s := &d2ir.Scalar{ s := &d2ir.Scalar{
parent: nil,
Value: d2ast.FlatUnquotedString(scalStr), Value: d2ast.FlatUnquotedString(scalStr),
} }
a := &d2ir.Array{ a := &d2ir.Array{
Parent: nil,
Values: []d2ir.Value{ Values: []d2ir.Value{
&d2ir.Scalar{ &d2ir.Scalar{
parent: nil,
Value: &d2ast.Boolean{ Value: &d2ast.Boolean{
Value: true, Value: true,
}, },
@ -28,7 +26,6 @@ func TestCopy(t *testing.T) {
}, },
} }
m2 := &d2ir.Map{ m2 := &d2ir.Map{
Parent: nil,
Fields: []*d2ir.Field{ Fields: []*d2ir.Field{
{Primary: s}, {Primary: s},
}, },
@ -36,20 +33,17 @@ func TestCopy(t *testing.T) {
const keyStr = `Absence makes the heart grow frantic.` const keyStr = `Absence makes the heart grow frantic.`
f := &d2ir.Field{ f := &d2ir.Field{
Parent: nil,
Name: keyStr, Name: keyStr,
Primary: s, Primary: s,
Composite: a, Composite: a,
} }
e := &d2ir.Edge{ e := &d2ir.Edge{
Parent: nil,
Primary: s, Primary: s,
Map: m2, Map: m2,
} }
m := &d2ir.Map{ m := &d2ir.Map{
Parent: nil,
Fields: []*d2ir.Field{f}, Fields: []*d2ir.Field{f},
Edges: []*d2ir.Edge{e}, Edges: []*d2ir.Edge{e},
@ -58,20 +52,20 @@ func TestCopy(t *testing.T) {
m = m.Copy(nil).(*d2ir.Map) m = m.Copy(nil).(*d2ir.Map)
f.Name = `Many a wife thinks her husband is the world's greatest lover.` 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, m, m.Fields[0].Parent())
assert.Equal(t, keyStr, m.Fields[0].Name) 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].Primary.Parent())
assert.Equal(t, m.Fields[0], m.Fields[0].Composite.(*d2ir.Array).Parent) assert.Equal(t, m.Fields[0], m.Fields[0].Composite.(*d2ir.Array).Parent())
assert.Equal(t, assert.Equal(t,
m.Fields[0].Composite, m.Fields[0].Composite,
m.Fields[0].Composite.(*d2ir.Array).Values[0].(*d2ir.Scalar).parent, m.Fields[0].Composite.(*d2ir.Array).Values[0].(*d2ir.Scalar).Parent(),
) )
assert.Equal(t, m, m.Edges[0].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].Primary.Parent())
assert.Equal(t, m.Edges[0], m.Edges[0].Map.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, m.Edges[0].Map.Fields[0].Parent())
assert.Equal(t, m.Edges[0].Map.Fields[0], m.Edges[0].Map.Fields[0].Primary.parent) assert.Equal(t, m.Edges[0].Map.Fields[0], m.Edges[0].Map.Fields[0].Primary.Parent())
} }

View file

@ -45,7 +45,7 @@ func Parse(path string, r io.RuneReader, opts *ParseOptions) (*d2ast.Map, error)
} }
m := p.parseMap(true) m := p.parseMap(true)
if !p.err.empty() { if !p.err.Empty() {
return m, p.err return m, p.err
} }
return m, nil return m, nil
@ -57,7 +57,7 @@ func ParseKey(key string) (*d2ast.KeyPath, error) {
} }
k := p.parseKey() k := p.parseKey()
if !p.err.empty() { if !p.err.Empty() {
return nil, fmt.Errorf("failed to parse key %q: %w", key, p.err) return nil, fmt.Errorf("failed to parse key %q: %w", key, p.err)
} }
if k == nil { if k == nil {
@ -72,7 +72,7 @@ func ParseMapKey(mapKey string) (*d2ast.Key, error) {
} }
mk := p.parseMapKey() mk := p.parseMapKey()
if !p.err.empty() { if !p.err.Empty() {
return nil, fmt.Errorf("failed to parse map key %q: %w", mapKey, p.err) return nil, fmt.Errorf("failed to parse map key %q: %w", mapKey, p.err)
} }
if mk == nil { if mk == nil {
@ -87,7 +87,7 @@ func ParseValue(value string) (d2ast.Value, error) {
} }
v := p.parseValue() v := p.parseValue()
if !p.err.empty() { if !p.err.Empty() {
return nil, fmt.Errorf("failed to parse value %q: %w", value, p.err) return nil, fmt.Errorf("failed to parse value %q: %w", value, p.err)
} }
if v.Unbox() == nil { if v.Unbox() == nil {
@ -130,7 +130,7 @@ type ParseError struct {
Errors []d2ast.Error `json:"errs"` Errors []d2ast.Error `json:"errs"`
} }
func (pe ParseError) empty() bool { func (pe ParseError) Empty() bool {
return pe.IOError == nil && len(pe.Errors) == 0 return pe.IOError == nil && len(pe.Errors) == 0
} }

4
testdata/d2ir/TestApply/simple/nested.exp.json generated vendored Normal file
View file

@ -0,0 +1,4 @@
{
"fields": null,
"edges": null
}

8
testdata/d2ir/TestApply/simple/one.exp.json generated vendored Normal file
View file

@ -0,0 +1,8 @@
{
"fields": [
{
"name": "x"
}
],
"edges": null
}

View file

@ -0,0 +1,4 @@
{
"fields": null,
"edges": null
}