diff --git a/d2compiler/compile.go b/d2compiler/compile.go index 4c11ac5dd..9c27c1459 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -1,7 +1,6 @@ package d2compiler import ( - "encoding/json" "encoding/xml" "fmt" "io" @@ -65,8 +64,6 @@ func compileIR(ast *d2ast.Map, m *d2ir.Map) (*d2graph.Graph, error) { g := d2graph.NewGraph() g.AST = ast c.compileBoard(g, m) - b, _ := json.MarshalIndent(m, "", " ") - println("\033[1;31m--- DEBUG:", string(b), "\033[m") if len(c.err.Errors) > 0 { return nil, c.err } diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 08fef43fd..bbd0b4e16 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -3193,6 +3193,69 @@ hi: ${x} assert.Equal(t, "im a var", g.Objects[0].Label.Value) }, }, + { + // TODO: text before/after substitutions + name: "combined", + skip: true, + run: func(t *testing.T) { + g := assertCompile(t, ` +vars: { + x: im a var +} +hi: 1 ${x} 2 +`, "") + assert.Equal(t, "1 im a var 2", g.Objects[0].Label.Value) + }, + }, + { + name: "edge label", + run: func(t *testing.T) { + g := assertCompile(t, ` +vars: { + x: im a var +} +a -> b: ${x} +`, "") + assert.Equal(t, 1, len(g.Edges)) + assert.Equal(t, "im a var", g.Edges[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("override", 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} +hi: not a var +`, "") + assert.Equal(t, 1, len(g.Objects)) + assert.Equal(t, "not a var", g.Objects[0].Label.Value) + }, + }, } for _, tc := range tca { diff --git a/d2ir/compile.go b/d2ir/compile.go index 27c11233b..a19bda567 100644 --- a/d2ir/compile.go +++ b/d2ir/compile.go @@ -52,16 +52,15 @@ func Compile(ast *d2ast.Map, opts *CompileOptions) (*Map, error) { defer c.popImportStack() c.compileMap(m, ast, ast) - c.compileClasses(m) - c.compileVars(m) - c.compileSubstitutions(m) + c.overlayClasses(m) + c.overlayVars(m) if !c.err.Empty() { return nil, c.err } return m, nil } -func (c *compiler) compileClasses(m *Map) { +func (c *compiler) overlayClasses(m *Map) { classes := m.GetField("classes") if classes == nil || classes.Map() == nil { return @@ -94,49 +93,49 @@ func (c *compiler) compileClasses(m *Map) { l.Fields = append(l.Fields, base) } - c.compileClasses(l) + c.overlayClasses(l) } } -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 +func (c *compiler) resolveSubstitution(vars *Map, mk *d2ast.Key, substitution *d2ast.Substitution) d2ast.Scalar { + var resolved *Field + for _, p := range substitution.Path { + if vars == nil { + resolved = nil + break } - 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) + r := vars.GetField(p.Unbox().ScalarString()) + if r == nil { + resolved = nil + break + } + vars = r.Map() + resolved = r + } + if resolved == nil { + c.errorf(mk, "could not resolve variable %s", strings.Join(substitution.IDA(), ".")) + } else { + // TODO maps + return resolved.Primary().Value + } + return nil +} - // TODO maps - f.Primary_ = &Scalar{ - parent: f, - Value: resolved.Primary().Value, - } - } - ref.Context.Key.Value.Substitution = nil - } +func (c *compiler) compileVars(m *Map, ast *d2ast.Map) { + for _, n := range ast.Nodes { + if n.MapKey != nil && n.MapKey.Key != nil && len(n.MapKey.Key.Path) == 1 && strings.EqualFold(n.MapKey.Key.Path[0].Unbox().ScalarString(), "vars") { + c.compileKey(&RefContext{ + Key: n.MapKey, + Scope: ast, + ScopeMap: m, + ScopeAST: ast, + }) + break } } } -func (c *compiler) compileVars(m *Map) { +func (c *compiler) overlayVars(m *Map) { vars := m.GetField("vars") if vars == nil || vars.Map() == nil { return @@ -169,7 +168,7 @@ func (c *compiler) compileVars(m *Map) { l.Fields = append(l.Fields, base) } - c.compileVars(l) + c.overlayVars(l) } } @@ -184,6 +183,10 @@ func (c *compiler) overlay(base *Map, f *Field) { } func (c *compiler) compileMap(dst *Map, ast, scopeAST *d2ast.Map) { + // When compiling a new board, compile vars before all else, as it might be referenced + if NodeBoardKind(dst) != "" { + c.compileVars(dst, ast) + } for _, n := range ast.Nodes { switch { case n.MapKey != nil: @@ -279,7 +282,7 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext) c.compileMap(f.Map(), refctx.Key.Value.Map, scopeAST) switch NodeBoardKind(f) { case BoardScenario, BoardStep: - c.compileClasses(f.Map()) + c.overlayClasses(f.Map()) } } else if refctx.Key.Value.Import != nil { n, ok := c._import(refctx.Key.Value.Import) @@ -318,13 +321,18 @@ func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext) c.updateLinks(f.Map()) switch NodeBoardKind(f) { case BoardScenario, BoardStep: - c.compileClasses(f.Map()) + c.overlayClasses(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") + vars := ParentBoard(f).Map().GetField("vars") + resolved := c.resolveSubstitution(vars.Map(), refctx.Key, refctx.Key.Value.Substitution) + if resolved != nil { + f.Primary_ = &Scalar{ + parent: f, + Value: resolved, + } + } } else if refctx.Key.Value.ScalarBox().Unbox() != nil { // If the link is a board, we need to transform it into an absolute path. if f.Name == "link" { @@ -502,6 +510,15 @@ func (c *compiler) compileEdges(refctx *RefContext) { } } c.compileMap(e.Map_, refctx.Key.Value.Map, refctx.ScopeAST) + } else if refctx.Key.Value.Substitution != nil { + vars := ParentBoard(e).Map().GetField("vars") + resolved := c.resolveSubstitution(vars.Map(), refctx.Key, refctx.Key.Value.Substitution) + if resolved != nil { + e.Primary_ = &Scalar{ + parent: e, + Value: resolved, + } + } } else if refctx.Key.Value.ScalarBox().Unbox() != nil { e.Primary_ = &Scalar{ parent: e, diff --git a/testdata/d2compiler/TestCompile2/vars/basic/edge_label.exp.json b/testdata/d2compiler/TestCompile2/vars/basic/edge_label.exp.json new file mode 100644 index 000000000..55c12d65d --- /dev/null +++ b/testdata/d2compiler/TestCompile2/vars/basic/edge_label.exp.json @@ -0,0 +1,285 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,0:0:0-5:0:38", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,1:0:1-3:1:24", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,1:0:1-1:4:5", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,1:0:1-1:4:5", + "value": [ + { + "string": "vars", + "raw_string": "vars" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,1:6:7-3:1:24", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,2:2:11-2:13:22", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,2:2:11-2:3:12", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_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/edge_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/edge_label.d2,4:0:25-4:12:37", + "edges": [ + { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,4:0:25-4:6:31", + "src": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,4:0:25-4:1:26", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,4:0:25-4:1:26", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "src_arrow": "", + "dst": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,4:5:30-4:6:31", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,4:5:30-4:6:31", + "value": [ + { + "string": "b", + "raw_string": "b" + } + ] + } + } + ] + }, + "dst_arrow": ">" + } + ], + "primary": {}, + "value": { + "substitution": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,4:8:33-4:12:37", + "spread": false, + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,4:10:35-4:11:36", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + } + } + } + } + ] + }, + "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": [ + { + "index": 0, + "isCurve": false, + "src_arrow": false, + "dst_arrow": true, + "references": [ + { + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "im a var" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ], + "objects": [ + { + "id": "a", + "id_val": "a", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,4:0:25-4:1:26", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,4:0:25-4:1:26", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "a" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + { + "id": "b", + "id_val": "b", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,4:5:30-4:6:31", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/edge_label.d2,4:5:30-4:6:31", + "value": [ + { + "string": "b", + "raw_string": "b" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "b" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ] + }, + "err": null +} diff --git a/testdata/d2compiler/TestCompile2/vars/basic/label.exp.json b/testdata/d2compiler/TestCompile2/vars/basic/label.exp.json index 0270f685c..41f1864ae 100644 --- a/testdata/d2compiler/TestCompile2/vars/basic/label.exp.json +++ b/testdata/d2compiler/TestCompile2/vars/basic/label.exp.json @@ -87,7 +87,25 @@ ] }, "primary": {}, - "value": {} + "value": { + "substitution": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,4:4:29-4:8:33", + "spread": false, + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/basic/label.d2,4:6:31-4:7:32", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + } + } } } ] diff --git a/testdata/d2compiler/TestCompile2/vars/override/label.exp.json b/testdata/d2compiler/TestCompile2/vars/override/label.exp.json new file mode 100644 index 000000000..585e192ee --- /dev/null +++ b/testdata/d2compiler/TestCompile2/vars/override/label.exp.json @@ -0,0 +1,239 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,0:0:0-6:0:48", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,1:0:1-3:1:24", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,1:0:1-1:4:5", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,1:0:1-1:4:5", + "value": [ + { + "string": "vars", + "raw_string": "vars" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,1:6:7-3:1:24", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,2:2:11-2:13:22", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,2:2:11-2:3:12", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,2:2:11-2:3:12", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/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/override/label.d2,4:0:25-4:8:33", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,4:0:25-4:2:27", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,4:0:25-4:2:27", + "value": [ + { + "string": "hi", + "raw_string": "hi" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "substitution": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,4:4:29-4:8:33", + "spread": false, + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,4:6:31-4:7:32", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,5:0:34-5:13:47", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,5:0:34-5:2:36", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,5:0:34-5:2:36", + "value": [ + { + "string": "hi", + "raw_string": "hi" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,5:4:38-5:13:47", + "value": [ + { + "string": "not a var", + "raw_string": "not a var" + } + ] + } + } + } + } + ] + }, + "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/override/label.d2,4:0:25-4:2:27", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,4:0:25-4:2:27", + "value": [ + { + "string": "hi", + "raw_string": "hi" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + }, + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,5:0:34-5:2:36", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/vars/override/label.d2,5:0:34-5:2:36", + "value": [ + { + "string": "hi", + "raw_string": "hi" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "not a var" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ] + }, + "err": null +}