d2ir: IR Root wip

This commit is contained in:
Anmol Sethi 2023-01-18 04:32:12 -08:00
parent 180334a8e1
commit 748557d8f2
No known key found for this signature in database
GPG key ID: 25BC68888A99A8BA
4 changed files with 102 additions and 71 deletions

View file

@ -242,8 +242,7 @@ d2/testdata/d2compiler/TestCompile/no_dimensions_on_containers.d2:37:3: height c
} }
} }
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/shape_unquoted_hex.d2:3:10: missing value after colon expErr: `d2/testdata/d2compiler/TestCompile/shape_unquoted_hex.d2:3:10: missing value after colon`,
`,
}, },
{ {
name: "edge_unquoted_hex", name: "edge_unquoted_hex",
@ -254,8 +253,7 @@ d2/testdata/d2compiler/TestCompile/no_dimensions_on_containers.d2:37:3: height c
} }
} }
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/edge_unquoted_hex.d2:3:10: missing value after colon expErr: `d2/testdata/d2compiler/TestCompile/edge_unquoted_hex.d2:3:10: missing value after colon`,
`,
}, },
{ {
name: "blank_underscore", name: "blank_underscore",
@ -265,8 +263,7 @@ d2/testdata/d2compiler/TestCompile/no_dimensions_on_containers.d2:37:3: height c
_ _
} }
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/blank_underscore.d2:3:3: invalid use of parent "_" expErr: `d2/testdata/d2compiler/TestCompile/blank_underscore.d2:3:3: invalid use of parent "_"`,
`,
}, },
{ {
name: "image_non_style", name: "image_non_style",
@ -277,8 +274,7 @@ d2/testdata/d2compiler/TestCompile/no_dimensions_on_containers.d2:37:3: height c
name: y name: y
} }
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/image_non_style.d2:4:3: image shapes cannot have children. expErr: `d2/testdata/d2compiler/TestCompile/image_non_style.d2:4:3: image shapes cannot have children.`,
`,
}, },
{ {
name: "stroke-width", name: "stroke-width",
@ -303,8 +299,7 @@ d2/testdata/d2compiler/TestCompile/no_dimensions_on_containers.d2:37:3: height c
style.stroke-width: -1 style.stroke-width: -1
} }
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/illegal-stroke-width.d2:2:23: expected "stroke-width" to be a number between 0 and 15 expErr: `d2/testdata/d2compiler/TestCompile/illegal-stroke-width.d2:2:23: expected "stroke-width" to be a number between 0 and 15`,
`,
}, },
{ {
name: "underscore_parent_create", name: "underscore_parent_create",
@ -457,8 +452,7 @@ x: {
text: ` text: `
_.x _.x
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/underscore_parent_root.d2:2:1: parent "_" cannot be used in the root scope expErr: `d2/testdata/d2compiler/TestCompile/underscore_parent_root.d2:2:1: parent "_" cannot be used in the root scope`,
`,
}, },
{ {
name: "underscore_parent_middle_path", name: "underscore_parent_middle_path",
@ -468,8 +462,7 @@ x: {
y._.z y._.z
} }
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/underscore_parent_middle_path.d2:3:3: parent "_" can only be used in the beginning of paths, e.g. "_.x" expErr: `d2/testdata/d2compiler/TestCompile/underscore_parent_middle_path.d2:3:3: parent "_" can only be used in the beginning of paths, e.g. "_.x"`,
`,
}, },
{ {
name: "underscore_parent_sandwich_path", name: "underscore_parent_sandwich_path",
@ -479,8 +472,7 @@ x: {
_.z._ _.z._
} }
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/underscore_parent_sandwich_path.d2:3:3: parent "_" can only be used in the beginning of paths, e.g. "_.x" expErr: `d2/testdata/d2compiler/TestCompile/underscore_parent_sandwich_path.d2:3:3: parent "_" can only be used in the beginning of paths, e.g. "_.x"`,
`,
}, },
{ {
name: "underscore_edge", name: "underscore_edge",
@ -997,8 +989,7 @@ x -> y: {
text: `x: {shape: triangle} text: `x: {shape: triangle}
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/object_arrowhead_shape.d2:1:5: invalid shape, can only set "triangle" for arrowheads expErr: `d2/testdata/d2compiler/TestCompile/object_arrowhead_shape.d2:1:5: invalid shape, can only set "triangle" for arrowheads`,
`,
}, },
{ {
name: "edge_flat_label_arrowhead", name: "edge_flat_label_arrowhead",
@ -1084,8 +1075,7 @@ x -> y: {
space -> stars space -> stars
} }
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/nested_edge.d2:1:1: edges cannot be nested within another edge expErr: `d2/testdata/d2compiler/TestCompile/nested_edge.d2:1:1: edges cannot be nested within another edge`,
`,
}, },
{ {
name: "shape_edge_style", name: "shape_edge_style",
@ -1095,8 +1085,7 @@ x: {
style.animated: true style.animated: true
} }
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/shape_edge_style.d2:3:2: key "animated" can only be applied to edges expErr: `d2/testdata/d2compiler/TestCompile/shape_edge_style.d2:3:2: key "animated" can only be applied to edges`,
`,
}, },
{ {
name: "edge_chain_map", name: "edge_chain_map",
@ -1352,8 +1341,7 @@ x -> y: {
z z
} }
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/edge_map_non_reserved.d2:2:1: edge map keys must be reserved keywords expErr: `d2/testdata/d2compiler/TestCompile/edge_map_non_reserved.d2:2:1: edge map keys must be reserved keywords`,
`,
}, },
{ {
name: "url_link", name: "url_link",
@ -1398,8 +1386,7 @@ x -> y: {
text: `x.near: txop-center text: `x.near: txop-center
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/near_bad_constant.d2:1:1: near key "txop-center" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right expErr: `d2/testdata/d2compiler/TestCompile/near_bad_constant.d2:1:1: near key "txop-center" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right`,
`,
}, },
{ {
name: "near_bad_container", name: "near_bad_container",
@ -1409,8 +1396,7 @@ x -> y: {
y y
} }
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/near_bad_container.d2:1:1: constant near keys cannot be set on shapes with children expErr: `d2/testdata/d2compiler/TestCompile/near_bad_container.d2:1:1: constant near keys cannot be set on shapes with children`,
`,
}, },
{ {
name: "near_bad_connected", name: "near_bad_connected",
@ -1420,16 +1406,14 @@ x -> y: {
} }
x -> y x -> y
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/near_bad_connected.d2:1:1: constant near keys cannot be set on connected shapes expErr: `d2/testdata/d2compiler/TestCompile/near_bad_connected.d2:1:1: constant near keys cannot be set on connected shapes`,
`,
}, },
{ {
name: "nested_near_constant", name: "nested_near_constant",
text: `x.y.near: top-center text: `x.y.near: top-center
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/nested_near_constant.d2:1:1: constant near keys can only be set on root level shapes expErr: `d2/testdata/d2compiler/TestCompile/nested_near_constant.d2:1:1: constant near keys can only be set on root level shapes`,
`,
}, },
{ {
name: "reserved_icon_near_style", name: "reserved_icon_near_style",
@ -1477,15 +1461,13 @@ y
expErr: `d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:3:9: bad icon url "::????:::%%orange": parse "::????:::%%orange": missing protocol scheme expErr: `d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:3:9: bad icon url "::????:::%%orange": parse "::????:::%%orange": missing protocol scheme
d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:4:18: expected "opacity" to be a number between 0.0 and 1.0 d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:4:18: expected "opacity" to be a number between 0.0 and 1.0
d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:5:18: expected "opacity" to be a number between 0.0 and 1.0 d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:5:18: expected "opacity" to be a number between 0.0 and 1.0
d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:1:1: near key "y" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:1:1: near key "y" must be the absolute path to a shape or one of the following constants: top-left, top-center, top-right, center-left, center-right, bottom-left, bottom-center, bottom-right`,
`,
}, },
{ {
name: "errors/missing_shape_icon", name: "errors/missing_shape_icon",
text: `x.shape: image`, text: `x.shape: image`,
expErr: `d2/testdata/d2compiler/TestCompile/errors/missing_shape_icon.d2:1:1: image shape must include an "icon" field expErr: `d2/testdata/d2compiler/TestCompile/errors/missing_shape_icon.d2:1:1: image shape must include an "icon" field`,
`,
}, },
{ {
name: "edge_in_column", name: "edge_in_column",
@ -1501,8 +1483,7 @@ d2/testdata/d2compiler/TestCompile/errors/reserved_icon_style.d2:1:1: near key "
text: `x: {style.opacity: 0.4} text: `x: {style.opacity: 0.4}
y -> x.style y -> x.style
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/edge_to_style.d2:2:1: cannot connect to reserved keyword expErr: `d2/testdata/d2compiler/TestCompile/edge_to_style.d2:2:1: cannot connect to reserved keyword`,
`,
}, },
{ {
name: "escaped_id", name: "escaped_id",
@ -1683,8 +1664,7 @@ x.y -> a.b: {
text: `SVP1.style.shape: oval text: `SVP1.style.shape: oval
SVP1.style.3d: true`, SVP1.style.3d: true`,
expErr: `d2/testdata/d2compiler/TestCompile/3d_oval.d2:2:1: key "3d" can only be applied to squares and rectangles expErr: `d2/testdata/d2compiler/TestCompile/3d_oval.d2:2:1: key "3d" can only be applied to squares and rectangles`,
`,
}, { }, {
name: "edge_column_index", name: "edge_column_index",
text: `src: { text: `src: {
@ -1741,8 +1721,7 @@ dst.id <-> src.dst_id
} }
b -> x.a b -> x.a
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/leaky_sequence.d2:5:1: connections within sequence diagrams can connect only to other objects within the same sequence diagram expErr: `d2/testdata/d2compiler/TestCompile/leaky_sequence.d2:5:1: connections within sequence diagrams can connect only to other objects within the same sequence diagram`,
`,
}, },
{ {
name: "sequence_scoping", name: "sequence_scoping",
@ -1819,8 +1798,7 @@ choo: {
text: `x: { text: `x: {
direction: diagonal direction: diagonal
}`, }`,
expErr: `d2/testdata/d2compiler/TestCompile/invalid_direction.d2:2:14: direction must be one of up, down, right, left, got "diagonal" expErr: `d2/testdata/d2compiler/TestCompile/invalid_direction.d2:2:14: direction must be one of up, down, right, left, got "diagonal"`,
`,
}, },
{ {
name: "self-referencing", name: "self-referencing",
@ -1869,8 +1847,7 @@ choo: {
test_id: varchar(64) {constraint: [primary_key, foreign_key]} test_id: varchar(64) {constraint: [primary_key, foreign_key]}
} }
`, `,
expErr: `d2/testdata/d2compiler/TestCompile/sql-panic.d2:3:27: constraint value must be a string expErr: `d2/testdata/d2compiler/TestCompile/sql-panic.d2:3:27: constraint value must be a string`,
`,
}, },
{ {
name: "wrong_column_index", name: "wrong_column_index",

View file

@ -276,6 +276,25 @@ func testCompileEdges(t *testing.T) {
func testCompileLayers(t *testing.T) { func testCompileLayers(t *testing.T) {
t.Parallel() t.Parallel()
t.Run("errs", func(t *testing.T) {
tca := []testCase{
{
name: "bad_edge/1",
run: func(t testing.TB) {
_, err := compile(t, `layers.x -> layers.y`)
assert.ErrorString(t, err, `TestCompile/layer/errs/bad_edge/1.d2:1:1: cannot create edges between layers, scenarios or steps`)
},
},
{
name: "bad_edge/2",
run: func(t testing.TB) {
_, err := compile(t, `layers -> scenarios`)
assert.ErrorString(t, err, `TestCompile/layer/errs/bad_edge/2.d2:1:1: cannot create edges between layers, scenarios or steps`)
},
},
}
runa(t, tca)
})
tca := []testCase{ tca := []testCase{
{ {
name: "root", name: "root",

View file

@ -121,25 +121,28 @@ func (m *Map) Copy(newp Node) Node {
// Root reports whether the Map is the root of the D2 tree. // Root reports whether the Map is the root of the D2 tree.
func (m *Map) Root() bool { func (m *Map) Root() bool {
return ParentMap(m) == nil return m.parent == nil
} }
// Layer reports whether the Map represents the root of a layer. // Layer reports whether n represents the root of a layer.
func (m *Map) Layer() bool { func IsLayer(n Node) bool {
f := ParentField(m) switch n := n.(type) {
if f == nil { case *Field:
return true n = ParentField(n)
} if n != nil {
f = ParentField(f) switch n.Name {
if f == nil { case "layers", "scenarios", "steps":
return false return true
} }
switch f.Name { }
case "layers", "scenarios", "steps": case *Map:
return true f := ParentField(n)
default: if f == nil {
return false return true
}
return IsLayer(f)
} }
return false
} }
type Field struct { type Field struct {
@ -318,8 +321,8 @@ func (a *Array) Copy(newp Node) Node {
} }
type FieldReference struct { type FieldReference struct {
String d2ast.String `json:"string"` String d2ast.String `json:"string"`
KeyPath *d2ast.KeyPath `json:"key_path"` KeyPath *d2ast.KeyPath `json:"key_path"`
Context *RefContext `json:"context"` Context *RefContext `json:"context"`
} }
@ -484,7 +487,7 @@ func (m *Map) ensureField(i int, kp *d2ast.KeyPath, refctx *RefContext) (*Field,
switch head { switch head {
case "layers", "scenarios", "steps": case "layers", "scenarios", "steps":
if !m.Layer() { if !IsLayer(m) {
return nil, d2parser.Errorf(kp.Path[i].Unbox(), "%s is only allowed at a layer root", head) return nil, d2parser.Errorf(kp.Path[i].Unbox(), "%s is only allowed at a layer root", head)
} }
} }
@ -614,6 +617,27 @@ func (m *Map) CreateEdge(eid *EdgeID, refctx *RefContext) (*Edge, error) {
return f_m.CreateEdge(eid, refctx) return f_m.CreateEdge(eid, refctx)
} }
ij := hasLayerKeyword(eid.SrcPath)
if ij != -1 {
return nil, d2parser.Errorf(refctx.Edge.Src.Path[ij].Unbox(), "cannot create edges between layers, scenarios or steps")
}
src := m.GetField(eid.SrcPath...)
if IsLayer(src) {
return nil, d2parser.Errorf(refctx.Edge.Src, "cannot create edges between layers, scenarios or steps")
}
ij = hasLayerKeyword(eid.DstPath)
if ij != -1 {
return nil, d2parser.Errorf(refctx.Edge.Dst.Path[ij].Unbox(), "cannot create edges between layers, scenarios or steps")
}
dst := m.GetField(eid.DstPath...)
if IsLayer(dst) {
return nil, d2parser.Errorf(refctx.Edge.Dst, "cannot create edges between layers, scenarios or steps")
}
if ParentLayer(src) != ParentLayer(dst) {
return nil, d2parser.Errorf(refctx.Edge, "cannot create edges between layers, scenarios or steps")
}
eid.Index = nil eid.Index = nil
ea := m.GetEdges(eid) ea := m.GetEdges(eid)
index := len(ea) index := len(ea)
@ -774,13 +798,13 @@ func ParentField(n Node) *Field {
return nil return nil
} }
func ParentLayer(n Node) *Map { func ParentLayer(n Node) Node {
for { for {
m := ParentMap(n) m := ParentMap(n)
if m == nil { if m == nil {
return nil return nil
} }
if m.Layer() { if IsLayer(m) {
return m return m
} }
n = m n = m
@ -797,3 +821,13 @@ func countUnderscores(p []string) int {
} }
return count return count
} }
func hasLayerKeyword(ida []string) int {
for i := range ida {
switch ida[i] {
case "layers", "scenarios", "steps":
return i
}
}
return -1
}

View file

@ -147,11 +147,12 @@ func (pe ParseError) Error() string {
var sb strings.Builder var sb strings.Builder
if pe.IOError != nil { if pe.IOError != nil {
sb.WriteString(pe.IOError.Error()) sb.WriteString(pe.IOError.Error())
sb.WriteByte('\n')
} }
for _, err := range pe.Errors { for i, err := range pe.Errors {
if pe.IOError != nil || i > 0 {
sb.WriteByte('\n')
}
sb.WriteString(err.Error()) sb.WriteString(err.Error())
sb.WriteByte('\n')
} }
return sb.String() return sb.String()
} }