label works
This commit is contained in:
parent
ce93835777
commit
c9793b9a87
7 changed files with 365 additions and 0 deletions
|
|
@ -423,6 +423,7 @@ func (s *BlockString) value() {}
|
||||||
func (a *Array) value() {}
|
func (a *Array) value() {}
|
||||||
func (m *Map) value() {}
|
func (m *Map) value() {}
|
||||||
func (i *Import) value() {}
|
func (i *Import) value() {}
|
||||||
|
func (i *Substitution) value() {}
|
||||||
|
|
||||||
func (n *Null) scalar() {}
|
func (n *Null) scalar() {}
|
||||||
func (b *Boolean) scalar() {}
|
func (b *Boolean) scalar() {}
|
||||||
|
|
@ -901,6 +902,7 @@ type ValueBox struct {
|
||||||
Array *Array `json:"array,omitempty"`
|
Array *Array `json:"array,omitempty"`
|
||||||
Map *Map `json:"map,omitempty"`
|
Map *Map `json:"map,omitempty"`
|
||||||
Import *Import `json:"import,omitempty"`
|
Import *Import `json:"import,omitempty"`
|
||||||
|
Substitution *Substitution `json:"substitution,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (vb ValueBox) Unbox() Value {
|
func (vb ValueBox) Unbox() Value {
|
||||||
|
|
@ -925,6 +927,8 @@ func (vb ValueBox) Unbox() Value {
|
||||||
return vb.Map
|
return vb.Map
|
||||||
case vb.Import != nil:
|
case vb.Import != nil:
|
||||||
return vb.Import
|
return vb.Import
|
||||||
|
case vb.Substitution != nil:
|
||||||
|
return vb.Substitution
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -953,6 +957,8 @@ func MakeValueBox(v Value) ValueBox {
|
||||||
vb.Map = v
|
vb.Map = v
|
||||||
case *Import:
|
case *Import:
|
||||||
vb.Import = v
|
vb.Import = v
|
||||||
|
case *Substitution:
|
||||||
|
vb.Substitution = v
|
||||||
}
|
}
|
||||||
return vb
|
return vb
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package d2compiler
|
package d2compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -64,6 +65,8 @@ func compileIR(ast *d2ast.Map, m *d2ir.Map) (*d2graph.Graph, error) {
|
||||||
g := d2graph.NewGraph()
|
g := d2graph.NewGraph()
|
||||||
g.AST = ast
|
g.AST = ast
|
||||||
c.compileBoard(g, m)
|
c.compileBoard(g, m)
|
||||||
|
b, _ := json.MarshalIndent(m, "", " ")
|
||||||
|
println("\033[1;31m--- DEBUG:", string(b), "\033[m")
|
||||||
if len(c.err.Errors) > 0 {
|
if len(c.err.Errors) > 0 {
|
||||||
return nil, c.err
|
return nil, c.err
|
||||||
}
|
}
|
||||||
|
|
@ -277,6 +280,26 @@ func (c *compiler) compileField(obj *d2graph.Object, f *d2ir.Field) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
} else if f.Name == "vars" {
|
||||||
|
if f.Map() != nil {
|
||||||
|
if len(f.Map().Edges) > 0 {
|
||||||
|
c.errorf(f.Map().Edges[0].LastRef().AST(), "vars cannot contain an edge")
|
||||||
|
}
|
||||||
|
// for _, varField := range f.Map().Fields {
|
||||||
|
// if varField.Map() != nil {
|
||||||
|
// c.errorf(varField.LastRef().AST(), "vars must be simple")
|
||||||
|
// }
|
||||||
|
// for _, cf := range classesField.Map().Fields {
|
||||||
|
// if _, ok := d2graph.ReservedKeywords[cf.Name]; !ok {
|
||||||
|
// c.errorf(cf.LastRef().AST(), "%s is an invalid class field, must be reserved keyword", cf.Name)
|
||||||
|
// }
|
||||||
|
// if cf.Name == "class" {
|
||||||
|
// c.errorf(cf.LastRef().AST(), `"class" cannot appear within "classes"`)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
return
|
||||||
} else if isReserved {
|
} else if isReserved {
|
||||||
c.compileReserved(&obj.Attributes, f)
|
c.compileReserved(&obj.Attributes, f)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -2744,6 +2744,7 @@ func TestCompile2(t *testing.T) {
|
||||||
t.Run("boards", testBoards)
|
t.Run("boards", testBoards)
|
||||||
t.Run("seqdiagrams", testSeqDiagrams)
|
t.Run("seqdiagrams", testSeqDiagrams)
|
||||||
t.Run("nulls", testNulls)
|
t.Run("nulls", testNulls)
|
||||||
|
t.Run("vars", testVars)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testBoards(t *testing.T) {
|
func testBoards(t *testing.T) {
|
||||||
|
|
@ -3168,6 +3169,78 @@ scenarios: {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testVars(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("basic", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tca := []struct {
|
||||||
|
name string
|
||||||
|
skip bool
|
||||||
|
run func(t *testing.T)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "label",
|
||||||
|
run: func(t *testing.T) {
|
||||||
|
g := assertCompile(t, `
|
||||||
|
vars: {
|
||||||
|
x: im a var
|
||||||
|
}
|
||||||
|
hi: ${x}
|
||||||
|
`, "")
|
||||||
|
assert.Equal(t, 1, len(g.Objects))
|
||||||
|
assert.Equal(t, "im a var", g.Objects[0].Label.Value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tca {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if tc.skip {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
tc.run(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("errors", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tca := []struct {
|
||||||
|
name string
|
||||||
|
skip bool
|
||||||
|
run func(t *testing.T)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing",
|
||||||
|
run: func(t *testing.T) {
|
||||||
|
assertCompile(t, `
|
||||||
|
vars: {
|
||||||
|
x: hey
|
||||||
|
}
|
||||||
|
hi: ${z}
|
||||||
|
`, "d2/testdata/d2compiler/TestCompile2/vars/errors/missing.d2:5:1: could not resolve variable z")
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tca {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if tc.skip {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
tc.run(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func assertCompile(t *testing.T, text string, expErr string) *d2graph.Graph {
|
func assertCompile(t *testing.T, text string, expErr string) *d2graph.Graph {
|
||||||
d2Path := fmt.Sprintf("d2/testdata/d2compiler/%v.d2", t.Name())
|
d2Path := fmt.Sprintf("d2/testdata/d2compiler/%v.d2", t.Name())
|
||||||
g, err := d2compiler.Compile(d2Path, strings.NewReader(text), nil)
|
g, err := d2compiler.Compile(d2Path, strings.NewReader(text), nil)
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,8 @@ func Compile(ast *d2ast.Map, opts *CompileOptions) (*Map, error) {
|
||||||
|
|
||||||
c.compileMap(m, ast, ast)
|
c.compileMap(m, ast, ast)
|
||||||
c.compileClasses(m)
|
c.compileClasses(m)
|
||||||
|
c.compileVars(m)
|
||||||
|
c.compileSubstitutions(m)
|
||||||
if !c.err.Empty() {
|
if !c.err.Empty() {
|
||||||
return nil, c.err
|
return nil, c.err
|
||||||
}
|
}
|
||||||
|
|
@ -96,6 +98,81 @@ func (c *compiler) compileClasses(m *Map) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *compiler) compileSubstitutions(m *Map) {
|
||||||
|
vars := m.GetField("vars")
|
||||||
|
for _, f := range m.Fields {
|
||||||
|
// No substitutions within vars itself
|
||||||
|
if f.Name == "vars" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, ref := range f.References {
|
||||||
|
if ref.Context.Key != nil && ref.Context.Key.Value.Substitution != nil {
|
||||||
|
var resolved *Field
|
||||||
|
m := vars
|
||||||
|
for _, p := range ref.Context.Key.Value.Substitution.Path {
|
||||||
|
r := m.Map().GetField(p.Unbox().ScalarString())
|
||||||
|
if r == nil {
|
||||||
|
resolved = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m = r
|
||||||
|
resolved = r
|
||||||
|
}
|
||||||
|
if resolved == nil {
|
||||||
|
c.errorf(ref.Context.Key, "could not resolve variable %s", strings.Join(ref.Context.Key.Value.Substitution.IDA(), "."))
|
||||||
|
} else {
|
||||||
|
// TODO do i need this
|
||||||
|
// ref.Context.Key.Value = d2ast.MakeValueBox(resolved.Primary().Value)
|
||||||
|
|
||||||
|
// TODO maps
|
||||||
|
f.Primary_ = &Scalar{
|
||||||
|
parent: f,
|
||||||
|
Value: resolved.Primary().Value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ref.Context.Key.Value.Substitution = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *compiler) compileVars(m *Map) {
|
||||||
|
vars := m.GetField("vars")
|
||||||
|
if vars == nil || vars.Map() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
layersField := m.GetField("layers")
|
||||||
|
if layersField == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
layers := layersField.Map()
|
||||||
|
if layers == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, lf := range layers.Fields {
|
||||||
|
if lf.Map() == nil || lf.Primary() != nil {
|
||||||
|
c.errorf(lf.References[0].Context.Key, "invalid layer")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
l := lf.Map()
|
||||||
|
lVars := l.GetField("vars")
|
||||||
|
|
||||||
|
if lVars == nil {
|
||||||
|
lVars = vars.Copy(l).(*Field)
|
||||||
|
l.Fields = append(l.Fields, lVars)
|
||||||
|
} else {
|
||||||
|
base := vars.Copy(l).(*Field)
|
||||||
|
OverlayMap(base.Map(), lVars.Map())
|
||||||
|
l.DeleteField("vars")
|
||||||
|
l.Fields = append(l.Fields, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.compileVars(l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *compiler) overlay(base *Map, f *Field) {
|
func (c *compiler) overlay(base *Map, f *Field) {
|
||||||
if f.Map() == nil || f.Primary() != nil {
|
if f.Map() == nil || f.Primary() != nil {
|
||||||
c.errorf(f.References[0].Context.Key, "invalid %s", NodeBoardKind(f))
|
c.errorf(f.References[0].Context.Key, "invalid %s", NodeBoardKind(f))
|
||||||
|
|
@ -244,6 +321,10 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext)
|
||||||
c.compileClasses(f.Map())
|
c.compileClasses(f.Map())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if refctx.Key.Value.Substitution != nil {
|
||||||
|
// b, _ := json.MarshalIndent(refctx.Key.Value.Substitution.IDA(), "", " ")
|
||||||
|
// println("\033[1;31m--- DEBUG:", string(b), "\033[m")
|
||||||
|
// println("\033[1;31m--- DEBUG:", "=======what===============", "\033[m")
|
||||||
} else if refctx.Key.Value.ScalarBox().Unbox() != nil {
|
} else if refctx.Key.Value.ScalarBox().Unbox() != nil {
|
||||||
// If the link is a board, we need to transform it into an absolute path.
|
// If the link is a board, we need to transform it into an absolute path.
|
||||||
if f.Name == "link" {
|
if f.Name == "link" {
|
||||||
|
|
|
||||||
|
|
@ -1596,6 +1596,9 @@ func (p *parser) parseValue() d2ast.ValueBox {
|
||||||
case '@':
|
case '@':
|
||||||
box.Import = p.parseImport(false)
|
box.Import = p.parseImport(false)
|
||||||
return box
|
return box
|
||||||
|
case '$':
|
||||||
|
box.Substitution = p.parseSubstitution(false)
|
||||||
|
return box
|
||||||
}
|
}
|
||||||
|
|
||||||
p.replay(r)
|
p.replay(r)
|
||||||
|
|
|
||||||
168
testdata/d2compiler/TestCompile2/vars/basic/label.exp.json
generated
vendored
Normal file
168
testdata/d2compiler/TestCompile2/vars/basic/label.exp.json
generated
vendored
Normal file
|
|
@ -0,0 +1,168 @@
|
||||||
|
{
|
||||||
|
"graph": {
|
||||||
|
"name": "",
|
||||||
|
"isFolderOnly": false,
|
||||||
|
"ast": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,0:0:0-5:0:34",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"map_key": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,1:0:1-3:1:24",
|
||||||
|
"key": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,1:0:1-1:4:5",
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"unquoted_string": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,1:0:1-1:4:5",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"string": "vars",
|
||||||
|
"raw_string": "vars"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"primary": {},
|
||||||
|
"value": {
|
||||||
|
"map": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,1:6:7-3:1:24",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"map_key": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,2:2:11-2:13:22",
|
||||||
|
"key": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,2:2:11-2:3:12",
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"unquoted_string": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,2:2:11-2:3:12",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"string": "x",
|
||||||
|
"raw_string": "x"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"primary": {},
|
||||||
|
"value": {
|
||||||
|
"unquoted_string": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,2:5:14-2:13:22",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"string": "im a var",
|
||||||
|
"raw_string": "im a var"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"map_key": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,4:0:25-4:8:33",
|
||||||
|
"key": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,4:0:25-4:2:27",
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"unquoted_string": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,4:0:25-4:2:27",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"string": "hi",
|
||||||
|
"raw_string": "hi"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"primary": {},
|
||||||
|
"value": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"id": "",
|
||||||
|
"id_val": "",
|
||||||
|
"attributes": {
|
||||||
|
"label": {
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"labelDimensions": {
|
||||||
|
"width": 0,
|
||||||
|
"height": 0
|
||||||
|
},
|
||||||
|
"style": {},
|
||||||
|
"near_key": null,
|
||||||
|
"shape": {
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"direction": {
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"constraint": null
|
||||||
|
},
|
||||||
|
"zIndex": 0
|
||||||
|
},
|
||||||
|
"edges": null,
|
||||||
|
"objects": [
|
||||||
|
{
|
||||||
|
"id": "hi",
|
||||||
|
"id_val": "hi",
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"key": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,4:0:25-4:2:27",
|
||||||
|
"path": [
|
||||||
|
{
|
||||||
|
"unquoted_string": {
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,4:0:25-4:2:27",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"string": "hi",
|
||||||
|
"raw_string": "hi"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"key_path_index": 0,
|
||||||
|
"map_key_edge_index": -1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attributes": {
|
||||||
|
"label": {
|
||||||
|
"value": "im a var"
|
||||||
|
},
|
||||||
|
"labelDimensions": {
|
||||||
|
"width": 0,
|
||||||
|
"height": 0
|
||||||
|
},
|
||||||
|
"style": {},
|
||||||
|
"near_key": null,
|
||||||
|
"shape": {
|
||||||
|
"value": "rectangle"
|
||||||
|
},
|
||||||
|
"direction": {
|
||||||
|
"value": ""
|
||||||
|
},
|
||||||
|
"constraint": null
|
||||||
|
},
|
||||||
|
"zIndex": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"err": null
|
||||||
|
}
|
||||||
11
testdata/d2compiler/TestCompile2/vars/errors/missing.exp.json
generated
vendored
Normal file
11
testdata/d2compiler/TestCompile2/vars/errors/missing.exp.json
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"graph": null,
|
||||||
|
"err": {
|
||||||
|
"errs": [
|
||||||
|
{
|
||||||
|
"range": "d2/testdata/d2compiler/TestCompile2/vars/errors/missing.d2,4:0:20-4:8:28",
|
||||||
|
"errmsg": "d2/testdata/d2compiler/TestCompile2/vars/errors/missing.d2:5:1: could not resolve variable z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue