d2ir: wip
This commit is contained in:
parent
4b50748dd0
commit
83ef53dc40
8 changed files with 139 additions and 87 deletions
|
|
@ -24,7 +24,10 @@ func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) {
|
|||
func Apply(dst *Map, ast *d2ast.Map) error {
|
||||
var c compiler
|
||||
c.apply(dst, ast)
|
||||
return c.err
|
||||
if !c.err.Empty() {
|
||||
return c.err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *compiler) apply(dst *Map, ast *d2ast.Map) {
|
||||
|
|
|
|||
|
|
@ -7,20 +7,17 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"oss.terrastruct.com/util-go/assert"
|
||||
"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)
|
||||
run func(testing.TB, *d2ir.Map)
|
||||
}
|
||||
|
||||
func TestApply(t *testing.T) {
|
||||
|
|
@ -30,12 +27,13 @@ func TestApply(t *testing.T) {
|
|||
}
|
||||
|
||||
func testApplySimple(t *testing.T) {
|
||||
tcs := []testCase{
|
||||
t.Parallel()
|
||||
|
||||
tca := []testCase{
|
||||
{
|
||||
name: "one",
|
||||
text: `x`,
|
||||
|
||||
exp: func(t testing.TB, m *d2ir.Map, err error) {
|
||||
run: func(t testing.TB, m *d2ir.Map) {
|
||||
err := parse(t, m, `x`)
|
||||
assert.Success(t, err)
|
||||
assertField(t, m, 1, 0, nil)
|
||||
|
||||
|
|
@ -44,9 +42,8 @@ func testApplySimple(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "nested",
|
||||
text: `x.y -> z.p`,
|
||||
|
||||
exp: func(t testing.TB, m *d2ir.Map, err error) {
|
||||
run: func(t testing.TB, m *d2ir.Map) {
|
||||
err := parse(t, m, `x.y -> z.p`)
|
||||
assert.Success(t, err)
|
||||
assertField(t, m, 4, 1, nil)
|
||||
|
||||
|
|
@ -65,9 +62,8 @@ func testApplySimple(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "underscore_parent",
|
||||
text: `x._ -> z`,
|
||||
|
||||
exp: func(t testing.TB, m *d2ir.Map, err error) {
|
||||
run: func(t testing.TB, m *d2ir.Map) {
|
||||
err := parse(t, m, `x._ -> z`)
|
||||
assert.Success(t, err)
|
||||
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) {
|
||||
for _, tc := range tcs {
|
||||
func runa(t *testing.T, tca []testCase) {
|
||||
for _, tc := range tca {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
run(t, tc)
|
||||
m := &d2ir.Map{}
|
||||
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())
|
||||
ast, err := d2parser.Parse(d2Path, strings.NewReader(tc.text), nil)
|
||||
if err != nil {
|
||||
tc.exp(t, nil, err)
|
||||
t.FailNow()
|
||||
return
|
||||
}
|
||||
ast, err := d2parser.Parse(d2Path, strings.NewReader(text), nil)
|
||||
assert.Success(t, err)
|
||||
|
||||
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
|
||||
return err
|
||||
}
|
||||
|
||||
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 {
|
||||
t.Helper()
|
||||
|
||||
m := d2ir.NodeToMap(n)
|
||||
p := d2ir.NodeToPrimary(n)
|
||||
var m *d2ir.Map
|
||||
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 ok bool
|
||||
if len(ida) > 0 {
|
||||
f = m.Get(ida)
|
||||
if f == nil {
|
||||
t.Fatalf("expected field %#v in map %#v but not found", ida, m)
|
||||
f, ok = m.Get(ida)
|
||||
if !ok {
|
||||
t.Fatalf("expected field %v in map %s", ida, m)
|
||||
}
|
||||
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 {
|
||||
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())
|
||||
}
|
||||
assert.Equal(t, nfields, m.FieldCount())
|
||||
assert.Equal(t, nedges, m.EdgeCount())
|
||||
if !p.Equal(makeScalar(primary)) {
|
||||
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 {
|
||||
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)
|
||||
var m *d2ir.Map
|
||||
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)
|
||||
}
|
||||
case *d2ir.Map:
|
||||
m = n
|
||||
default:
|
||||
t.Fatalf("unexpected d2ir.Node %T", n)
|
||||
}
|
||||
|
||||
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())
|
||||
e, ok := m.GetEdge(eid)
|
||||
if !ok {
|
||||
t.Fatalf("expected edge %v in map %s but not found", eid, m)
|
||||
}
|
||||
|
||||
assert.Equal(t, nfields, e.Map.FieldCount())
|
||||
if !e.Primary.Equal(makeScalar(primary)) {
|
||||
t.Fatalf("expected primary %#v but %#v", primary, e.Primary)
|
||||
}
|
||||
|
|
|
|||
34
d2ir/d2ir.go
34
d2ir/d2ir.go
|
|
@ -1,6 +1,8 @@
|
|||
package d2ir
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"oss.terrastruct.com/d2/d2ast"
|
||||
|
|
@ -51,6 +53,7 @@ func (n *Edge) node() {}
|
|||
func (n *Array) node() {}
|
||||
func (n *Map) node() {}
|
||||
|
||||
func (n *Scalar) Parent() Parent { return n.parent }
|
||||
func (n *Field) Parent() Parent { return n.parent }
|
||||
func (n *Edge) 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 {
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
@ -113,10 +127,10 @@ type Field struct {
|
|||
|
||||
Name string `json:"name"`
|
||||
|
||||
Primary *Scalar `json:"primary"`
|
||||
Composite Composite `json:"composite"`
|
||||
Primary *Scalar `json:"primary,omitempty"`
|
||||
Composite Composite `json:"composite,omitempty"`
|
||||
|
||||
Refs []KeyReference `json:"refs"`
|
||||
Refs []KeyReference `json:"refs,omitempty"`
|
||||
}
|
||||
|
||||
func (f *Field) Copy(newp Parent) Node {
|
||||
|
|
@ -203,10 +217,10 @@ type Edge struct {
|
|||
|
||||
ID *EdgeID `json:"edge_id"`
|
||||
|
||||
Primary *Scalar `json:"primary"`
|
||||
Map *Map `json:"map"`
|
||||
Primary *Scalar `json:"primary,omitempty"`
|
||||
Map *Map `json:"map,omitempty"`
|
||||
|
||||
Refs []EdgeReference `json:"refs"`
|
||||
Refs []EdgeReference `json:"refs,omitempty"`
|
||||
}
|
||||
|
||||
func (e *Edge) Copy(newp Parent) Node {
|
||||
|
|
@ -384,3 +398,11 @@ func (m *Map) GetEdge(eid *EdgeID) (*Edge, bool) {
|
|||
}
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,10 @@ package d2ir_test
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"oss.terrastruct.com/util-go/assert"
|
||||
|
||||
"oss.terrastruct.com/d2/d2ast"
|
||||
"oss.terrastruct.com/d2/d2ir"
|
||||
"oss.terrastruct.com/d2/internal/assert"
|
||||
)
|
||||
|
||||
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.`
|
||||
s := &d2ir.Scalar{
|
||||
parent: nil,
|
||||
Value: d2ast.FlatUnquotedString(scalStr),
|
||||
Value: d2ast.FlatUnquotedString(scalStr),
|
||||
}
|
||||
a := &d2ir.Array{
|
||||
Parent: nil,
|
||||
Values: []d2ir.Value{
|
||||
&d2ir.Scalar{
|
||||
parent: nil,
|
||||
Value: &d2ast.Boolean{
|
||||
Value: true,
|
||||
},
|
||||
|
|
@ -28,7 +26,6 @@ func TestCopy(t *testing.T) {
|
|||
},
|
||||
}
|
||||
m2 := &d2ir.Map{
|
||||
Parent: nil,
|
||||
Fields: []*d2ir.Field{
|
||||
{Primary: s},
|
||||
},
|
||||
|
|
@ -36,20 +33,17 @@ func TestCopy(t *testing.T) {
|
|||
|
||||
const keyStr = `Absence makes the heart grow frantic.`
|
||||
f := &d2ir.Field{
|
||||
Parent: nil,
|
||||
Name: keyStr,
|
||||
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},
|
||||
|
|
@ -58,20 +52,20 @@ func TestCopy(t *testing.T) {
|
|||
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, 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], 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,
|
||||
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, 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)
|
||||
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())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ func Parse(path string, r io.RuneReader, opts *ParseOptions) (*d2ast.Map, error)
|
|||
}
|
||||
|
||||
m := p.parseMap(true)
|
||||
if !p.err.empty() {
|
||||
if !p.err.Empty() {
|
||||
return m, p.err
|
||||
}
|
||||
return m, nil
|
||||
|
|
@ -57,7 +57,7 @@ func ParseKey(key string) (*d2ast.KeyPath, error) {
|
|||
}
|
||||
|
||||
k := p.parseKey()
|
||||
if !p.err.empty() {
|
||||
if !p.err.Empty() {
|
||||
return nil, fmt.Errorf("failed to parse key %q: %w", key, p.err)
|
||||
}
|
||||
if k == nil {
|
||||
|
|
@ -72,7 +72,7 @@ func ParseMapKey(mapKey string) (*d2ast.Key, error) {
|
|||
}
|
||||
|
||||
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)
|
||||
}
|
||||
if mk == nil {
|
||||
|
|
@ -87,7 +87,7 @@ func ParseValue(value string) (d2ast.Value, error) {
|
|||
}
|
||||
|
||||
v := p.parseValue()
|
||||
if !p.err.empty() {
|
||||
if !p.err.Empty() {
|
||||
return nil, fmt.Errorf("failed to parse value %q: %w", value, p.err)
|
||||
}
|
||||
if v.Unbox() == nil {
|
||||
|
|
@ -130,7 +130,7 @@ type ParseError struct {
|
|||
Errors []d2ast.Error `json:"errs"`
|
||||
}
|
||||
|
||||
func (pe ParseError) empty() bool {
|
||||
func (pe ParseError) Empty() bool {
|
||||
return pe.IOError == nil && len(pe.Errors) == 0
|
||||
}
|
||||
|
||||
|
|
|
|||
4
testdata/d2ir/TestApply/simple/nested.exp.json
generated
vendored
Normal file
4
testdata/d2ir/TestApply/simple/nested.exp.json
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"fields": null,
|
||||
"edges": null
|
||||
}
|
||||
8
testdata/d2ir/TestApply/simple/one.exp.json
generated
vendored
Normal file
8
testdata/d2ir/TestApply/simple/one.exp.json
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"fields": [
|
||||
{
|
||||
"name": "x"
|
||||
}
|
||||
],
|
||||
"edges": null
|
||||
}
|
||||
4
testdata/d2ir/TestApply/simple/underscore_parent.exp.json
generated
vendored
Normal file
4
testdata/d2ir/TestApply/simple/underscore_parent.exp.json
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"fields": null,
|
||||
"edges": null
|
||||
}
|
||||
Loading…
Reference in a new issue