2023-01-16 11:52:37 +00:00
|
|
|
package d2ir
|
|
|
|
|
|
|
|
|
|
import (
|
2023-06-04 22:39:00 +00:00
|
|
|
"io/fs"
|
2023-07-14 20:08:26 +00:00
|
|
|
"strconv"
|
2023-03-05 17:43:42 +00:00
|
|
|
"strings"
|
|
|
|
|
|
2023-07-30 20:52:42 +00:00
|
|
|
"oss.terrastruct.com/util-go/go2"
|
|
|
|
|
|
2023-01-16 11:52:37 +00:00
|
|
|
"oss.terrastruct.com/d2/d2ast"
|
2023-03-03 01:56:32 +00:00
|
|
|
"oss.terrastruct.com/d2/d2format"
|
2023-01-16 11:52:37 +00:00
|
|
|
"oss.terrastruct.com/d2/d2parser"
|
2023-07-14 20:08:26 +00:00
|
|
|
"oss.terrastruct.com/d2/d2themes"
|
|
|
|
|
"oss.terrastruct.com/d2/d2themes/d2themescatalog"
|
2023-01-16 11:52:37 +00:00
|
|
|
)
|
|
|
|
|
|
2023-07-30 20:52:42 +00:00
|
|
|
type globContext struct {
|
2023-08-17 02:17:41 +00:00
|
|
|
root *globContext
|
2023-07-30 20:52:42 +00:00
|
|
|
refctx *RefContext
|
2023-08-17 02:17:41 +00:00
|
|
|
|
2023-07-30 20:52:42 +00:00
|
|
|
// Set of BoardIDA that this glob has already applied to.
|
|
|
|
|
appliedFields map[string]struct{}
|
|
|
|
|
// Set of Edge IDs that this glob has already applied to.
|
|
|
|
|
appliedEdges map[string]struct{}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-16 11:52:37 +00:00
|
|
|
type compiler struct {
|
2023-06-04 22:39:00 +00:00
|
|
|
err *d2parser.ParseError
|
|
|
|
|
|
2023-12-25 06:40:35 +00:00
|
|
|
fs fs.FS
|
|
|
|
|
imports []string
|
2023-06-04 22:39:00 +00:00
|
|
|
// importStack is used to detect cyclic imports.
|
|
|
|
|
importStack []string
|
2024-04-17 22:55:39 +00:00
|
|
|
seenImports map[string]struct{}
|
2023-08-02 17:26:45 +00:00
|
|
|
utf16Pos bool
|
2023-07-30 20:16:56 +00:00
|
|
|
|
2023-07-30 20:52:42 +00:00
|
|
|
// Stack of globs that must be recomputed at each new object in and below the current scope.
|
|
|
|
|
globContextStack [][]*globContext
|
|
|
|
|
// Used to prevent field globs causing infinite loops.
|
|
|
|
|
globRefContextStack []*RefContext
|
|
|
|
|
// Used to check whether ampersands are allowed in the current map.
|
2023-08-17 21:10:09 +00:00
|
|
|
mapRefContextStack []*RefContext
|
|
|
|
|
lazyGlobBeingApplied bool
|
2023-06-04 22:39:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type CompileOptions struct {
|
2023-08-02 17:26:45 +00:00
|
|
|
UTF16Pos bool
|
2023-06-04 22:39:00 +00:00
|
|
|
// Pass nil to disable imports.
|
|
|
|
|
FS fs.FS
|
2023-01-16 11:52:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) {
|
2023-01-18 10:06:44 +00:00
|
|
|
c.err.Errors = append(c.err.Errors, d2parser.Errorf(n, f, v...).(d2ast.Error))
|
2023-01-16 11:52:37 +00:00
|
|
|
}
|
|
|
|
|
|
2023-12-25 05:48:14 +00:00
|
|
|
func Compile(ast *d2ast.Map, opts *CompileOptions) (*Map, []string, error) {
|
2023-06-04 22:39:00 +00:00
|
|
|
if opts == nil {
|
|
|
|
|
opts = &CompileOptions{}
|
|
|
|
|
}
|
|
|
|
|
c := &compiler{
|
|
|
|
|
err: &d2parser.ParseError{},
|
|
|
|
|
fs: opts.FS,
|
|
|
|
|
|
2024-04-17 22:55:39 +00:00
|
|
|
seenImports: make(map[string]struct{}),
|
2023-08-02 17:26:45 +00:00
|
|
|
utf16Pos: opts.UTF16Pos,
|
2023-06-04 22:39:00 +00:00
|
|
|
}
|
2023-01-24 06:45:21 +00:00
|
|
|
m := &Map{}
|
|
|
|
|
m.initRoot()
|
2023-08-17 21:10:09 +00:00
|
|
|
m.parent.(*Field).References[0].Context_.Scope = ast
|
|
|
|
|
m.parent.(*Field).References[0].Context_.ScopeAST = ast
|
2023-06-04 22:39:00 +00:00
|
|
|
|
|
|
|
|
c.pushImportStack(&d2ast.Import{
|
|
|
|
|
Path: []*d2ast.StringBox{d2ast.RawStringBox(ast.GetRange().Path, true)},
|
|
|
|
|
})
|
|
|
|
|
defer c.popImportStack()
|
|
|
|
|
|
2023-06-20 03:06:26 +00:00
|
|
|
c.compileMap(m, ast, ast)
|
2023-07-12 00:19:34 +00:00
|
|
|
c.compileSubstitutions(m, nil)
|
2023-07-11 04:18:18 +00:00
|
|
|
c.overlayClasses(m)
|
2023-01-16 11:52:37 +00:00
|
|
|
if !c.err.Empty() {
|
2023-12-25 05:48:14 +00:00
|
|
|
return nil, nil, c.err
|
2023-01-16 11:52:37 +00:00
|
|
|
}
|
2023-12-25 06:40:35 +00:00
|
|
|
return m, c.imports, nil
|
2023-01-18 01:39:31 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-11 04:18:18 +00:00
|
|
|
func (c *compiler) overlayClasses(m *Map) {
|
2023-02-06 21:32:08 +00:00
|
|
|
classes := m.GetField("classes")
|
|
|
|
|
if classes == nil || classes.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 {
|
2023-08-17 21:10:09 +00:00
|
|
|
c.errorf(lf.References[0].Context_.Key, "invalid layer")
|
2023-02-06 21:32:08 +00:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
l := lf.Map()
|
|
|
|
|
lClasses := l.GetField("classes")
|
|
|
|
|
|
|
|
|
|
if lClasses == nil {
|
|
|
|
|
lClasses = classes.Copy(l).(*Field)
|
|
|
|
|
l.Fields = append(l.Fields, lClasses)
|
|
|
|
|
} else {
|
|
|
|
|
base := classes.Copy(l).(*Field)
|
|
|
|
|
OverlayMap(base.Map(), lClasses.Map())
|
|
|
|
|
l.DeleteField("classes")
|
|
|
|
|
l.Fields = append(l.Fields, base)
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-11 04:18:18 +00:00
|
|
|
c.overlayClasses(l)
|
2023-02-06 21:32:08 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-12 00:19:34 +00:00
|
|
|
func (c *compiler) compileSubstitutions(m *Map, varsStack []*Map) {
|
|
|
|
|
for _, f := range m.Fields {
|
|
|
|
|
if f.Name == "vars" && f.Map() != nil {
|
|
|
|
|
varsStack = append([]*Map{f.Map()}, varsStack...)
|
|
|
|
|
}
|
2023-11-01 19:05:45 +00:00
|
|
|
}
|
2024-04-17 18:24:50 +00:00
|
|
|
for i := 0; i < len(m.Fields); i++ {
|
|
|
|
|
f := m.Fields[i]
|
2023-07-12 00:19:34 +00:00
|
|
|
if f.Primary() != nil {
|
2024-04-17 18:24:50 +00:00
|
|
|
removed := c.resolveSubstitutions(varsStack, f)
|
|
|
|
|
if removed {
|
|
|
|
|
i--
|
|
|
|
|
}
|
2023-07-12 00:19:34 +00:00
|
|
|
}
|
2023-07-13 05:20:55 +00:00
|
|
|
if arr, ok := f.Composite.(*Array); ok {
|
|
|
|
|
for _, val := range arr.Values {
|
|
|
|
|
if scalar, ok := val.(*Scalar); ok {
|
2024-04-17 18:24:50 +00:00
|
|
|
removed := c.resolveSubstitutions(varsStack, scalar)
|
|
|
|
|
if removed {
|
|
|
|
|
i--
|
|
|
|
|
}
|
2023-07-13 05:20:55 +00:00
|
|
|
}
|
|
|
|
|
}
|
2023-07-13 17:11:08 +00:00
|
|
|
} else if f.Map() != nil {
|
2023-07-12 18:55:58 +00:00
|
|
|
// don't resolve substitutions in vars with the current scope of vars
|
2023-07-12 06:26:06 +00:00
|
|
|
if f.Name == "vars" {
|
|
|
|
|
c.compileSubstitutions(f.Map(), varsStack[1:])
|
2023-07-14 20:08:26 +00:00
|
|
|
c.validateConfigs(f.Map().GetField("d2-config"))
|
2023-07-12 06:26:06 +00:00
|
|
|
} else {
|
|
|
|
|
c.compileSubstitutions(f.Map(), varsStack)
|
|
|
|
|
}
|
2023-07-12 00:19:34 +00:00
|
|
|
}
|
2023-07-11 20:32:07 +00:00
|
|
|
}
|
2023-07-12 00:19:34 +00:00
|
|
|
for _, e := range m.Edges {
|
|
|
|
|
if e.Primary() != nil {
|
2023-07-12 18:55:58 +00:00
|
|
|
c.resolveSubstitutions(varsStack, e)
|
2023-07-12 00:19:34 +00:00
|
|
|
}
|
2023-07-12 00:33:01 +00:00
|
|
|
if e.Map() != nil {
|
|
|
|
|
c.compileSubstitutions(e.Map(), varsStack)
|
|
|
|
|
}
|
2023-07-11 20:32:07 +00:00
|
|
|
}
|
2023-07-12 00:19:34 +00:00
|
|
|
}
|
2023-07-11 20:32:07 +00:00
|
|
|
|
2023-07-14 20:08:26 +00:00
|
|
|
func (c *compiler) validateConfigs(configs *Field) {
|
|
|
|
|
if configs == nil || configs.Map() == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if NodeBoardKind(ParentMap(ParentMap(configs))) == "" {
|
|
|
|
|
c.errorf(configs.LastRef().AST(), `"%s" can only appear at root vars`, configs.Name)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, f := range configs.Map().Fields {
|
|
|
|
|
var val string
|
|
|
|
|
if f.Primary() == nil {
|
2023-12-13 20:17:22 +00:00
|
|
|
if f.Name != "theme-overrides" && f.Name != "dark-theme-overrides" {
|
2023-07-14 20:08:26 +00:00
|
|
|
c.errorf(f.LastRef().AST(), `"%s" needs a value`, f.Name)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
val = f.Primary().Value.ScalarString()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch f.Name {
|
|
|
|
|
case "sketch", "center":
|
|
|
|
|
_, err := strconv.ParseBool(val)
|
|
|
|
|
if err != nil {
|
|
|
|
|
c.errorf(f.LastRef().AST(), `expected a boolean for "%s", got "%s"`, f.Name, val)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2023-12-13 20:17:22 +00:00
|
|
|
case "theme-overrides", "dark-theme-overrides":
|
2023-07-14 20:08:26 +00:00
|
|
|
if f.Map() == nil {
|
|
|
|
|
c.errorf(f.LastRef().AST(), `"%s" needs a map`, f.Name)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
case "theme-id", "dark-theme-id":
|
|
|
|
|
valInt, err := strconv.Atoi(val)
|
|
|
|
|
if err != nil {
|
|
|
|
|
c.errorf(f.LastRef().AST(), `expected an integer for "%s", got "%s"`, f.Name, val)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if d2themescatalog.Find(int64(valInt)) == (d2themes.Theme{}) {
|
|
|
|
|
c.errorf(f.LastRef().AST(), `%d is not a valid theme ID`, valInt)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
case "pad":
|
|
|
|
|
_, err := strconv.Atoi(val)
|
|
|
|
|
if err != nil {
|
|
|
|
|
c.errorf(f.LastRef().AST(), `expected an integer for "%s", got "%s"`, f.Name, val)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
case "layout-engine":
|
|
|
|
|
default:
|
|
|
|
|
c.errorf(f.LastRef().AST(), `"%s" is not a valid config`, f.Name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-17 18:24:50 +00:00
|
|
|
func (c *compiler) resolveSubstitutions(varsStack []*Map, node Node) (removedField bool) {
|
2023-07-12 00:33:01 +00:00
|
|
|
var subbed bool
|
|
|
|
|
var resolvedField *Field
|
|
|
|
|
|
2023-07-12 18:55:58 +00:00
|
|
|
switch s := node.Primary().Value.(type) {
|
2023-07-12 00:19:34 +00:00
|
|
|
case *d2ast.UnquotedString:
|
|
|
|
|
for i, box := range s.Value {
|
2023-07-11 20:32:07 +00:00
|
|
|
if box.Substitution != nil {
|
2023-07-12 00:33:01 +00:00
|
|
|
for _, vars := range varsStack {
|
|
|
|
|
resolvedField = c.resolveSubstitution(vars, box.Substitution)
|
|
|
|
|
if resolvedField != nil {
|
2023-07-13 15:35:16 +00:00
|
|
|
if resolvedField.Primary() != nil {
|
|
|
|
|
if _, ok := resolvedField.Primary().Value.(*d2ast.Null); ok {
|
|
|
|
|
resolvedField = nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-12 00:33:01 +00:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-12 06:26:06 +00:00
|
|
|
if resolvedField == nil {
|
2023-07-12 18:55:58 +00:00
|
|
|
c.errorf(node.LastRef().AST(), `could not resolve variable "%s"`, strings.Join(box.Substitution.IDA(), "."))
|
2023-07-12 00:33:01 +00:00
|
|
|
return
|
2023-07-11 20:32:07 +00:00
|
|
|
}
|
2023-07-12 21:53:28 +00:00
|
|
|
if box.Substitution.Spread {
|
|
|
|
|
if resolvedField.Composite == nil {
|
2023-07-13 15:37:22 +00:00
|
|
|
c.errorf(box.Substitution, "cannot spread non-composite")
|
2023-07-12 21:53:28 +00:00
|
|
|
continue
|
|
|
|
|
}
|
2023-07-13 05:20:55 +00:00
|
|
|
switch n := node.(type) {
|
|
|
|
|
case *Scalar: // Array value
|
|
|
|
|
resolvedArr, ok := resolvedField.Composite.(*Array)
|
|
|
|
|
if !ok {
|
|
|
|
|
c.errorf(box.Substitution, "cannot spread non-array into array")
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
arr := n.parent.(*Array)
|
|
|
|
|
for i, s := range arr.Values {
|
|
|
|
|
if s == n {
|
|
|
|
|
arr.Values = append(append(arr.Values[:i], resolvedArr.Values...), arr.Values[i+1:]...)
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case *Field:
|
|
|
|
|
if resolvedField.Map() != nil {
|
|
|
|
|
OverlayMap(ParentMap(n), resolvedField.Map())
|
|
|
|
|
}
|
|
|
|
|
// Remove the placeholder field
|
|
|
|
|
m := n.parent.(*Map)
|
|
|
|
|
for i, f2 := range m.Fields {
|
|
|
|
|
if n == f2 {
|
|
|
|
|
m.Fields = append(m.Fields[:i], m.Fields[i+1:]...)
|
2024-04-17 18:24:50 +00:00
|
|
|
removedField = true
|
2023-07-13 05:20:55 +00:00
|
|
|
break
|
|
|
|
|
}
|
2023-07-12 21:53:28 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-12 18:55:58 +00:00
|
|
|
if resolvedField.Primary() == nil {
|
2023-07-13 21:11:14 +00:00
|
|
|
if resolvedField.Composite == nil {
|
|
|
|
|
c.errorf(node.LastRef().AST(), `cannot substitute variable without value: "%s"`, strings.Join(box.Substitution.IDA(), "."))
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-07-12 18:55:58 +00:00
|
|
|
if len(s.Value) > 1 {
|
2023-07-13 21:11:14 +00:00
|
|
|
c.errorf(node.LastRef().AST(), `cannot substitute composite variable "%s" as part of a string`, strings.Join(box.Substitution.IDA(), "."))
|
2023-07-12 18:55:58 +00:00
|
|
|
return
|
|
|
|
|
}
|
2023-07-13 05:20:55 +00:00
|
|
|
switch n := node.(type) {
|
|
|
|
|
case *Field:
|
|
|
|
|
n.Primary_ = nil
|
|
|
|
|
case *Edge:
|
|
|
|
|
n.Primary_ = nil
|
|
|
|
|
}
|
2023-07-12 06:26:06 +00:00
|
|
|
} else {
|
2023-07-18 16:24:36 +00:00
|
|
|
if i == 0 && len(s.Value) == 1 {
|
|
|
|
|
node.Primary().Value = resolvedField.Primary().Value
|
|
|
|
|
} else {
|
|
|
|
|
s.Value[i].String = go2.Pointer(resolvedField.Primary().Value.ScalarString())
|
|
|
|
|
subbed = true
|
|
|
|
|
}
|
2023-07-12 18:55:58 +00:00
|
|
|
}
|
|
|
|
|
if resolvedField.Composite != nil {
|
|
|
|
|
switch n := node.(type) {
|
|
|
|
|
case *Field:
|
|
|
|
|
n.Composite = resolvedField.Composite
|
|
|
|
|
case *Edge:
|
|
|
|
|
if resolvedField.Composite.Map() == nil {
|
|
|
|
|
c.errorf(node.LastRef().AST(), `cannot substitute array variable "%s" to an edge`, strings.Join(box.Substitution.IDA(), "."))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
n.Map_ = resolvedField.Composite.Map()
|
|
|
|
|
}
|
2023-07-12 06:26:06 +00:00
|
|
|
}
|
2023-07-11 20:32:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
2023-07-11 20:52:29 +00:00
|
|
|
if subbed {
|
2023-07-12 00:19:34 +00:00
|
|
|
s.Coalesce()
|
2023-07-11 20:52:29 +00:00
|
|
|
}
|
2023-07-12 00:19:34 +00:00
|
|
|
case *d2ast.DoubleQuotedString:
|
|
|
|
|
for i, box := range s.Value {
|
2023-07-11 20:32:07 +00:00
|
|
|
if box.Substitution != nil {
|
2023-07-12 00:33:01 +00:00
|
|
|
for _, vars := range varsStack {
|
|
|
|
|
resolvedField = c.resolveSubstitution(vars, box.Substitution)
|
|
|
|
|
if resolvedField != nil {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-12 06:26:06 +00:00
|
|
|
if resolvedField == nil {
|
2023-07-12 18:55:58 +00:00
|
|
|
c.errorf(node.LastRef().AST(), `could not resolve variable "%s"`, strings.Join(box.Substitution.IDA(), "."))
|
2023-07-12 00:33:01 +00:00
|
|
|
return
|
2023-07-11 20:32:07 +00:00
|
|
|
}
|
2023-07-13 17:11:08 +00:00
|
|
|
if resolvedField.Primary() == nil && resolvedField.Composite != nil {
|
2023-07-12 18:55:58 +00:00
|
|
|
c.errorf(node.LastRef().AST(), `cannot substitute map variable "%s" in quotes`, strings.Join(box.Substitution.IDA(), "."))
|
2023-07-12 06:26:06 +00:00
|
|
|
return
|
|
|
|
|
}
|
2023-07-13 17:13:26 +00:00
|
|
|
s.Value[i].String = go2.Pointer(resolvedField.Primary().Value.ScalarString())
|
2023-07-12 06:26:06 +00:00
|
|
|
subbed = true
|
2023-07-11 20:32:07 +00:00
|
|
|
}
|
|
|
|
|
}
|
2023-07-11 20:52:29 +00:00
|
|
|
if subbed {
|
2023-07-12 00:19:34 +00:00
|
|
|
s.Coalesce()
|
2023-07-11 20:52:29 +00:00
|
|
|
}
|
2023-07-11 20:32:07 +00:00
|
|
|
}
|
2024-04-17 18:24:50 +00:00
|
|
|
return removedField
|
2023-07-11 20:32:07 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-12 00:33:01 +00:00
|
|
|
func (c *compiler) resolveSubstitution(vars *Map, substitution *d2ast.Substitution) *Field {
|
2023-07-12 06:26:06 +00:00
|
|
|
if vars == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i, p := range substitution.Path {
|
|
|
|
|
f := vars.GetField(p.Unbox().ScalarString())
|
|
|
|
|
if f == nil {
|
|
|
|
|
return nil
|
2023-07-11 02:24:21 +00:00
|
|
|
}
|
2023-07-13 15:35:16 +00:00
|
|
|
|
2023-07-12 06:26:06 +00:00
|
|
|
if i == len(substitution.Path)-1 {
|
|
|
|
|
return f
|
2023-07-11 04:18:18 +00:00
|
|
|
}
|
2023-07-12 06:26:06 +00:00
|
|
|
vars = f.Map()
|
2023-07-11 04:18:18 +00:00
|
|
|
}
|
2023-07-12 06:26:06 +00:00
|
|
|
return nil
|
2023-07-11 04:18:18 +00:00
|
|
|
}
|
|
|
|
|
|
2023-03-25 19:06:06 +00:00
|
|
|
func (c *compiler) overlay(base *Map, f *Field) {
|
|
|
|
|
if f.Map() == nil || f.Primary() != nil {
|
2023-08-17 21:10:09 +00:00
|
|
|
c.errorf(f.References[0].Context_.Key, "invalid %s", NodeBoardKind(f))
|
2023-01-18 15:15:16 +00:00
|
|
|
return
|
|
|
|
|
}
|
2023-03-25 19:06:06 +00:00
|
|
|
base = base.CopyBase(f)
|
2024-02-15 01:59:34 +00:00
|
|
|
// Certain fields should never carry forward.
|
|
|
|
|
// If you give your scenario a label, you don't want all steps in a scenario to be labeled the same.
|
|
|
|
|
base.DeleteField("label")
|
2023-03-25 19:06:06 +00:00
|
|
|
OverlayMap(base, f.Map())
|
|
|
|
|
f.Composite = base
|
2023-01-16 11:52:37 +00:00
|
|
|
}
|
|
|
|
|
|
2023-09-04 05:51:38 +00:00
|
|
|
func (g *globContext) copy() *globContext {
|
2023-08-16 22:34:48 +00:00
|
|
|
g2 := *g
|
2023-08-17 02:17:41 +00:00
|
|
|
g2.refctx = g.root.refctx.Copy()
|
2023-09-04 05:51:38 +00:00
|
|
|
return &g2
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g *globContext) prefixed(dst *Map) *globContext {
|
|
|
|
|
g2 := g.copy()
|
2023-08-16 22:34:48 +00:00
|
|
|
prefix := d2ast.MakeKeyPath(RelIDA(g2.refctx.ScopeMap, dst))
|
|
|
|
|
g2.refctx.Key = g2.refctx.Key.Copy()
|
|
|
|
|
if g2.refctx.Key.Key != nil {
|
|
|
|
|
prefix.Path = append(prefix.Path, g2.refctx.Key.Key.Path...)
|
|
|
|
|
}
|
|
|
|
|
if len(prefix.Path) > 0 {
|
|
|
|
|
g2.refctx.Key.Key = prefix
|
|
|
|
|
}
|
2023-09-04 05:51:38 +00:00
|
|
|
return g2
|
2023-08-16 22:34:48 +00:00
|
|
|
}
|
|
|
|
|
|
2023-08-30 07:31:36 +00:00
|
|
|
func (c *compiler) ampersandFilterMap(dst *Map, ast, scopeAST *d2ast.Map) bool {
|
|
|
|
|
for _, n := range ast.Nodes {
|
|
|
|
|
switch {
|
|
|
|
|
case n.MapKey != nil:
|
|
|
|
|
ok := c.ampersandFilter(&RefContext{
|
|
|
|
|
Key: n.MapKey,
|
|
|
|
|
Scope: ast,
|
|
|
|
|
ScopeMap: dst,
|
|
|
|
|
ScopeAST: scopeAST,
|
|
|
|
|
})
|
|
|
|
|
if !ok {
|
2023-11-27 02:25:31 +00:00
|
|
|
if len(c.mapRefContextStack) == 0 {
|
|
|
|
|
return false
|
|
|
|
|
}
|
2023-08-30 07:31:36 +00:00
|
|
|
// Unapply glob if appropriate.
|
|
|
|
|
gctx := c.getGlobContext(c.mapRefContextStack[len(c.mapRefContextStack)-1])
|
|
|
|
|
if gctx == nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
var ks string
|
|
|
|
|
if gctx.refctx.Key.HasTripleGlob() {
|
|
|
|
|
ks = d2format.Format(d2ast.MakeKeyPath(IDA(dst)))
|
|
|
|
|
} else {
|
|
|
|
|
ks = d2format.Format(d2ast.MakeKeyPath(BoardIDA(dst)))
|
|
|
|
|
}
|
|
|
|
|
delete(gctx.appliedFields, ks)
|
2024-03-05 01:51:05 +00:00
|
|
|
delete(gctx.appliedEdges, ks)
|
2023-08-30 07:31:36 +00:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-20 03:06:26 +00:00
|
|
|
func (c *compiler) compileMap(dst *Map, ast, scopeAST *d2ast.Map) {
|
2023-07-30 20:52:42 +00:00
|
|
|
var globs []*globContext
|
|
|
|
|
if len(c.globContextStack) > 0 {
|
2023-12-05 21:12:36 +00:00
|
|
|
previousGlobs := c.globContexts()
|
|
|
|
|
// A root layer with existing glob context stack implies it's an import
|
|
|
|
|
// In which case, the previous globs should be inherited (the else block)
|
|
|
|
|
if NodeBoardKind(dst) == BoardLayer && !dst.Root() {
|
2023-07-30 20:52:42 +00:00
|
|
|
for _, g := range previousGlobs {
|
|
|
|
|
if g.refctx.Key.HasTripleGlob() {
|
2023-08-16 22:34:48 +00:00
|
|
|
globs = append(globs, g.prefixed(dst))
|
2023-07-30 20:52:42 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if NodeBoardKind(dst) != "" {
|
|
|
|
|
// Make all globs relative to the scenario or step.
|
|
|
|
|
for _, g := range previousGlobs {
|
2023-08-16 22:34:48 +00:00
|
|
|
globs = append(globs, g.prefixed(dst))
|
2023-07-30 20:52:42 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
globs = append(globs, previousGlobs...)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
c.globContextStack = append(c.globContextStack, globs)
|
|
|
|
|
defer func() {
|
2023-09-04 05:51:38 +00:00
|
|
|
dst.globs = c.globContexts()
|
2023-07-30 20:52:42 +00:00
|
|
|
c.globContextStack = c.globContextStack[:len(c.globContextStack)-1]
|
|
|
|
|
}()
|
|
|
|
|
|
2023-08-30 07:31:36 +00:00
|
|
|
ok := c.ampersandFilterMap(dst, ast, scopeAST)
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
2023-07-29 23:32:30 +00:00
|
|
|
}
|
2023-08-17 21:42:52 +00:00
|
|
|
|
2023-01-16 11:52:37 +00:00
|
|
|
for _, n := range ast.Nodes {
|
|
|
|
|
switch {
|
|
|
|
|
case n.MapKey != nil:
|
2023-01-24 05:48:43 +00:00
|
|
|
c.compileKey(&RefContext{
|
|
|
|
|
Key: n.MapKey,
|
|
|
|
|
Scope: ast,
|
|
|
|
|
ScopeMap: dst,
|
2023-06-20 03:06:26 +00:00
|
|
|
ScopeAST: scopeAST,
|
2023-01-18 05:28:33 +00:00
|
|
|
})
|
2023-07-12 21:53:28 +00:00
|
|
|
case n.Substitution != nil:
|
|
|
|
|
// placeholder field to be resolved at the end
|
|
|
|
|
f := &Field{
|
|
|
|
|
parent: dst,
|
|
|
|
|
Primary_: &Scalar{
|
|
|
|
|
Value: &d2ast.UnquotedString{
|
|
|
|
|
Value: []d2ast.InterpolationBox{{Substitution: n.Substitution}},
|
|
|
|
|
},
|
|
|
|
|
},
|
2023-11-01 19:05:45 +00:00
|
|
|
References: []*FieldReference{{
|
|
|
|
|
Context_: &RefContext{
|
|
|
|
|
Scope: ast,
|
|
|
|
|
ScopeMap: dst,
|
2023-11-01 20:21:11 +00:00
|
|
|
ScopeAST: scopeAST,
|
2023-11-01 19:05:45 +00:00
|
|
|
},
|
|
|
|
|
}},
|
2023-07-12 21:53:28 +00:00
|
|
|
}
|
|
|
|
|
dst.Fields = append(dst.Fields, f)
|
2023-06-04 22:39:00 +00:00
|
|
|
case n.Import != nil:
|
|
|
|
|
impn, ok := c._import(n.Import)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if impn.Map() == nil {
|
|
|
|
|
c.errorf(n.Import, "cannot spread import non map into map")
|
|
|
|
|
continue
|
|
|
|
|
}
|
2023-09-04 05:51:38 +00:00
|
|
|
|
|
|
|
|
for _, gctx := range impn.Map().globs {
|
|
|
|
|
if !gctx.refctx.Key.HasTripleGlob() {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
gctx2 := gctx.copy()
|
|
|
|
|
gctx2.refctx.ScopeMap = dst
|
|
|
|
|
c.compileKey(gctx2.refctx)
|
|
|
|
|
c.ensureGlobContext(gctx2.refctx)
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-04 22:39:00 +00:00
|
|
|
OverlayMap(dst, impn.Map())
|
2023-06-06 23:49:12 +00:00
|
|
|
|
|
|
|
|
if impnf, ok := impn.(*Field); ok {
|
|
|
|
|
if impnf.Primary_ != nil {
|
|
|
|
|
dstf := ParentField(dst)
|
|
|
|
|
if dstf != nil {
|
|
|
|
|
dstf.Primary_ = impnf.Primary_
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-16 11:52:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-30 20:52:42 +00:00
|
|
|
func (c *compiler) globContexts() []*globContext {
|
|
|
|
|
return c.globContextStack[len(c.globContextStack)-1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *compiler) getGlobContext(refctx *RefContext) *globContext {
|
|
|
|
|
for _, gctx := range c.globContexts() {
|
|
|
|
|
if gctx.refctx.Equal(refctx) {
|
|
|
|
|
return gctx
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *compiler) ensureGlobContext(refctx *RefContext) *globContext {
|
|
|
|
|
gctx := c.getGlobContext(refctx)
|
|
|
|
|
if gctx != nil {
|
|
|
|
|
return gctx
|
|
|
|
|
}
|
|
|
|
|
gctx = &globContext{
|
|
|
|
|
refctx: refctx,
|
|
|
|
|
appliedFields: make(map[string]struct{}),
|
|
|
|
|
appliedEdges: make(map[string]struct{}),
|
|
|
|
|
}
|
2023-08-17 02:17:41 +00:00
|
|
|
gctx.root = gctx
|
2023-07-30 20:52:42 +00:00
|
|
|
c.globContextStack[len(c.globContextStack)-1] = append(c.globContexts(), gctx)
|
|
|
|
|
return gctx
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-24 05:48:43 +00:00
|
|
|
func (c *compiler) compileKey(refctx *RefContext) {
|
2023-07-30 20:52:42 +00:00
|
|
|
if refctx.Key.HasGlob() {
|
2023-08-16 22:34:48 +00:00
|
|
|
// These printlns are for debugging infinite loops.
|
2023-08-16 07:02:09 +00:00
|
|
|
// println("og", refctx.Edge, refctx.Key, refctx.Scope, refctx.ScopeMap, refctx.ScopeAST)
|
2023-07-30 20:52:42 +00:00
|
|
|
for _, refctx2 := range c.globRefContextStack {
|
2023-08-16 07:02:09 +00:00
|
|
|
// println("st", refctx2.Edge, refctx2.Key, refctx2.Scope, refctx2.ScopeMap, refctx2.ScopeAST)
|
2023-07-30 20:52:42 +00:00
|
|
|
if refctx.Equal(refctx2) {
|
|
|
|
|
// Break the infinite loop.
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-08-16 07:02:09 +00:00
|
|
|
// println("keys", d2format.Format(refctx2.Key), d2format.Format(refctx.Key))
|
2023-07-30 20:52:42 +00:00
|
|
|
}
|
|
|
|
|
c.globRefContextStack = append(c.globRefContextStack, refctx)
|
|
|
|
|
defer func() {
|
|
|
|
|
c.globRefContextStack = c.globRefContextStack[:len(c.globRefContextStack)-1]
|
|
|
|
|
}()
|
|
|
|
|
c.ensureGlobContext(refctx)
|
|
|
|
|
}
|
2023-08-16 22:34:48 +00:00
|
|
|
oldFields := refctx.ScopeMap.FieldCountRecursive()
|
|
|
|
|
oldEdges := refctx.ScopeMap.EdgeCountRecursive()
|
2023-01-18 05:28:33 +00:00
|
|
|
if len(refctx.Key.Edges) == 0 {
|
2023-01-24 05:48:43 +00:00
|
|
|
c.compileField(refctx.ScopeMap, refctx.Key.Key, refctx)
|
2023-01-16 11:52:37 +00:00
|
|
|
} else {
|
2023-01-24 05:48:43 +00:00
|
|
|
c.compileEdges(refctx)
|
2023-01-16 11:52:37 +00:00
|
|
|
}
|
2023-08-16 22:34:48 +00:00
|
|
|
if oldFields != refctx.ScopeMap.FieldCountRecursive() || oldEdges != refctx.ScopeMap.EdgeCountRecursive() {
|
|
|
|
|
for _, gctx2 := range c.globContexts() {
|
|
|
|
|
// println(d2format.Format(gctx2.refctx.Key), d2format.Format(refctx.Key))
|
2023-08-20 04:20:32 +00:00
|
|
|
old := c.lazyGlobBeingApplied
|
2023-08-17 21:10:09 +00:00
|
|
|
c.lazyGlobBeingApplied = true
|
2023-08-16 22:34:48 +00:00
|
|
|
c.compileKey(gctx2.refctx)
|
2023-08-20 04:20:32 +00:00
|
|
|
c.lazyGlobBeingApplied = old
|
2023-08-16 22:34:48 +00:00
|
|
|
}
|
|
|
|
|
}
|
2023-01-16 11:52:37 +00:00
|
|
|
}
|
|
|
|
|
|
2023-01-18 10:06:44 +00:00
|
|
|
func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext) {
|
2023-07-30 01:02:09 +00:00
|
|
|
if refctx.Key.Ampersand {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-30 20:52:42 +00:00
|
|
|
fa, err := dst.EnsureField(kp, refctx, true, c)
|
2023-01-16 11:52:37 +00:00
|
|
|
if err != nil {
|
2023-01-18 10:06:44 +00:00
|
|
|
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
|
2023-01-16 11:52:37 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-24 22:31:58 +00:00
|
|
|
for _, f := range fa {
|
|
|
|
|
c._compileField(f, refctx)
|
|
|
|
|
}
|
2023-06-23 17:51:55 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-29 23:32:30 +00:00
|
|
|
func (c *compiler) ampersandFilter(refctx *RefContext) bool {
|
|
|
|
|
if !refctx.Key.Ampersand {
|
|
|
|
|
return true
|
|
|
|
|
}
|
2023-07-30 20:52:42 +00:00
|
|
|
if len(c.mapRefContextStack) == 0 || !c.mapRefContextStack[len(c.mapRefContextStack)-1].Key.SupportsGlobFilters() {
|
2023-07-30 20:16:56 +00:00
|
|
|
c.errorf(refctx.Key, "glob filters cannot be used outside globs")
|
|
|
|
|
return false
|
|
|
|
|
}
|
2023-07-29 23:32:30 +00:00
|
|
|
if len(refctx.Key.Edges) > 0 {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-30 20:52:42 +00:00
|
|
|
fa, err := refctx.ScopeMap.EnsureField(refctx.Key.Key, refctx, false, c)
|
2023-07-29 23:32:30 +00:00
|
|
|
if err != nil {
|
|
|
|
|
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if len(fa) == 0 {
|
2023-08-17 21:42:52 +00:00
|
|
|
if refctx.Key.Key.Last().ScalarString() != "label" {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
kp := refctx.Key.Key.Copy()
|
|
|
|
|
kp.Path = kp.Path[:len(kp.Path)-1]
|
|
|
|
|
if len(kp.Path) == 0 {
|
2023-08-17 21:53:51 +00:00
|
|
|
n := refctx.ScopeMap.Parent()
|
|
|
|
|
switch n := n.(type) {
|
|
|
|
|
case *Field:
|
|
|
|
|
fa = append(fa, n)
|
|
|
|
|
case *Edge:
|
|
|
|
|
if n.Primary_ == nil {
|
|
|
|
|
if refctx.Key.Value.ScalarBox().Unbox().ScalarString() == "" {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if n.Primary_.Value.ScalarString() != refctx.Key.Value.ScalarBox().Unbox().ScalarString() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-17 21:42:52 +00:00
|
|
|
} else {
|
|
|
|
|
fa, err = refctx.ScopeMap.EnsureField(kp, refctx, false, c)
|
|
|
|
|
if err != nil {
|
|
|
|
|
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, f := range fa {
|
|
|
|
|
label := f.Name
|
|
|
|
|
if f.Primary_ != nil {
|
|
|
|
|
label = f.Primary_.Value.ScalarString()
|
|
|
|
|
}
|
|
|
|
|
if label != refctx.Key.Value.ScalarBox().Unbox().ScalarString() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true
|
2023-07-29 23:32:30 +00:00
|
|
|
}
|
|
|
|
|
for _, f := range fa {
|
|
|
|
|
ok := c._ampersandFilter(f, refctx)
|
|
|
|
|
if !ok {
|
|
|
|
|
return false
|
2023-07-29 22:09:29 +00:00
|
|
|
}
|
2023-07-29 23:32:30 +00:00
|
|
|
}
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *compiler) _ampersandFilter(f *Field, refctx *RefContext) bool {
|
2023-07-30 01:02:09 +00:00
|
|
|
if refctx.Key.Value.ScalarBox().Unbox() == nil {
|
2023-07-30 20:16:56 +00:00
|
|
|
c.errorf(refctx.Key, "glob filters cannot be composites")
|
2023-07-30 01:02:09 +00:00
|
|
|
return false
|
2023-07-29 22:09:29 +00:00
|
|
|
}
|
2023-07-30 01:02:09 +00:00
|
|
|
|
2023-07-30 20:16:56 +00:00
|
|
|
if a, ok := f.Composite.(*Array); ok {
|
2023-07-30 01:02:09 +00:00
|
|
|
for _, v := range a.Values {
|
|
|
|
|
if s, ok := v.(*Scalar); ok {
|
|
|
|
|
if refctx.Key.Value.ScalarBox().Unbox().ScalarString() == s.Value.ScalarString() {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-29 23:32:30 +00:00
|
|
|
}
|
2023-07-30 01:02:09 +00:00
|
|
|
}
|
|
|
|
|
|
2023-07-30 20:16:56 +00:00
|
|
|
if f.Primary_ == nil {
|
2023-07-30 01:02:09 +00:00
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-30 20:16:56 +00:00
|
|
|
if refctx.Key.Value.ScalarBox().Unbox().ScalarString() != f.Primary_.Value.ScalarString() {
|
2023-07-30 00:09:20 +00:00
|
|
|
return false
|
2023-07-29 23:32:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
2023-07-29 22:09:29 +00:00
|
|
|
|
2023-07-29 23:32:30 +00:00
|
|
|
func (c *compiler) _compileField(f *Field, refctx *RefContext) {
|
2023-08-30 07:31:36 +00:00
|
|
|
// In case of filters, we need to pass filters before continuing
|
|
|
|
|
if refctx.Key.Value.Map != nil && refctx.Key.Value.Map.HasFilter() {
|
|
|
|
|
if f.Map() == nil {
|
|
|
|
|
f.Composite = &Map{
|
|
|
|
|
parent: f,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
c.mapRefContextStack = append(c.mapRefContextStack, refctx)
|
|
|
|
|
ok := c.ampersandFilterMap(f.Map(), refctx.Key.Value.Map, refctx.ScopeAST)
|
|
|
|
|
c.mapRefContextStack = c.mapRefContextStack[:len(c.mapRefContextStack)-1]
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(refctx.Key.Edges) == 0 && (refctx.Key.Primary.Null != nil || refctx.Key.Value.Null != nil) {
|
2023-07-14 21:22:02 +00:00
|
|
|
// For vars, if we delete the field, it may just resolve to an outer scope var of the same name
|
|
|
|
|
// Instead we keep it around, so that resolveSubstitutions can find it
|
|
|
|
|
if !IsVar(ParentMap(f)) {
|
2023-06-24 22:31:58 +00:00
|
|
|
ParentMap(f).DeleteField(f.Name)
|
2023-07-14 21:22:02 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-18 10:06:44 +00:00
|
|
|
if refctx.Key.Primary.Unbox() != nil {
|
2023-08-17 21:10:09 +00:00
|
|
|
if c.ignoreLazyGlob(f) {
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-01-18 15:15:16 +00:00
|
|
|
f.Primary_ = &Scalar{
|
2023-01-16 11:52:37 +00:00
|
|
|
parent: f,
|
2023-01-18 10:06:44 +00:00
|
|
|
Value: refctx.Key.Primary.Unbox(),
|
2023-01-16 11:52:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
2023-08-30 07:31:36 +00:00
|
|
|
|
2023-01-18 10:06:44 +00:00
|
|
|
if refctx.Key.Value.Array != nil {
|
2023-01-16 11:52:37 +00:00
|
|
|
a := &Array{
|
|
|
|
|
parent: f,
|
|
|
|
|
}
|
2023-06-20 03:06:26 +00:00
|
|
|
c.compileArray(a, refctx.Key.Value.Array, refctx.ScopeAST)
|
2023-01-16 11:52:37 +00:00
|
|
|
f.Composite = a
|
2023-01-18 10:06:44 +00:00
|
|
|
} else if refctx.Key.Value.Map != nil {
|
2023-08-30 11:35:07 +00:00
|
|
|
scopeAST := refctx.Key.Value.Map
|
2023-01-18 15:15:16 +00:00
|
|
|
if f.Map() == nil {
|
|
|
|
|
f.Composite = &Map{
|
2023-01-16 11:52:37 +00:00
|
|
|
parent: f,
|
|
|
|
|
}
|
2023-08-30 11:35:07 +00:00
|
|
|
switch NodeBoardKind(f) {
|
|
|
|
|
case BoardScenario:
|
|
|
|
|
c.overlay(ParentBoard(f).Map(), f)
|
|
|
|
|
case BoardStep:
|
|
|
|
|
stepsMap := ParentMap(f)
|
|
|
|
|
for i := range stepsMap.Fields {
|
|
|
|
|
if stepsMap.Fields[i] == f {
|
|
|
|
|
if i == 0 {
|
|
|
|
|
c.overlay(ParentBoard(f).Map(), f)
|
|
|
|
|
} else {
|
|
|
|
|
c.overlay(stepsMap.Fields[i-1].Map(), f)
|
|
|
|
|
}
|
|
|
|
|
break
|
2023-03-25 19:06:06 +00:00
|
|
|
}
|
|
|
|
|
}
|
2023-08-30 11:35:07 +00:00
|
|
|
case BoardLayer:
|
|
|
|
|
default:
|
|
|
|
|
// If new board type, use that as the new scope AST, otherwise, carry on
|
|
|
|
|
scopeAST = refctx.ScopeAST
|
2023-03-25 19:06:06 +00:00
|
|
|
}
|
2023-08-30 12:08:22 +00:00
|
|
|
} else {
|
|
|
|
|
scopeAST = refctx.ScopeAST
|
2023-03-25 19:06:06 +00:00
|
|
|
}
|
2023-07-30 20:52:42 +00:00
|
|
|
c.mapRefContextStack = append(c.mapRefContextStack, refctx)
|
2023-06-20 03:06:26 +00:00
|
|
|
c.compileMap(f.Map(), refctx.Key.Value.Map, scopeAST)
|
2023-07-30 20:52:42 +00:00
|
|
|
c.mapRefContextStack = c.mapRefContextStack[:len(c.mapRefContextStack)-1]
|
2023-02-06 21:32:08 +00:00
|
|
|
switch NodeBoardKind(f) {
|
|
|
|
|
case BoardScenario, BoardStep:
|
2023-07-11 04:18:18 +00:00
|
|
|
c.overlayClasses(f.Map())
|
2023-02-06 21:32:08 +00:00
|
|
|
}
|
2023-06-04 22:39:00 +00:00
|
|
|
} else if refctx.Key.Value.Import != nil {
|
|
|
|
|
n, ok := c._import(refctx.Key.Value.Import)
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
switch n := n.(type) {
|
|
|
|
|
case *Field:
|
|
|
|
|
if n.Primary_ != nil {
|
2023-06-07 05:13:49 +00:00
|
|
|
f.Primary_ = n.Primary_.Copy(f).(*Scalar)
|
2023-06-04 22:39:00 +00:00
|
|
|
}
|
|
|
|
|
if n.Composite != nil {
|
|
|
|
|
f.Composite = n.Composite.Copy(f).(Composite)
|
|
|
|
|
}
|
|
|
|
|
case *Map:
|
2023-06-06 23:49:12 +00:00
|
|
|
f.Composite = &Map{
|
|
|
|
|
parent: f,
|
|
|
|
|
}
|
|
|
|
|
switch NodeBoardKind(f) {
|
|
|
|
|
case BoardScenario:
|
|
|
|
|
c.overlay(ParentBoard(f).Map(), f)
|
|
|
|
|
case BoardStep:
|
|
|
|
|
stepsMap := ParentMap(f)
|
|
|
|
|
for i := range stepsMap.Fields {
|
|
|
|
|
if stepsMap.Fields[i] == f {
|
|
|
|
|
if i == 0 {
|
|
|
|
|
c.overlay(ParentBoard(f).Map(), f)
|
|
|
|
|
} else {
|
|
|
|
|
c.overlay(stepsMap.Fields[i-1].Map(), f)
|
|
|
|
|
}
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
OverlayMap(f.Map(), n)
|
2023-06-07 05:13:49 +00:00
|
|
|
c.updateLinks(f.Map())
|
2023-06-06 23:49:12 +00:00
|
|
|
switch NodeBoardKind(f) {
|
|
|
|
|
case BoardScenario, BoardStep:
|
2023-07-11 04:18:18 +00:00
|
|
|
c.overlayClasses(f.Map())
|
2023-06-06 23:49:12 +00:00
|
|
|
}
|
2023-06-04 22:39:00 +00:00
|
|
|
}
|
2023-01-18 10:06:44 +00:00
|
|
|
} else if refctx.Key.Value.ScalarBox().Unbox() != nil {
|
2023-08-17 21:10:09 +00:00
|
|
|
if c.ignoreLazyGlob(f) {
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-01-18 15:15:16 +00:00
|
|
|
f.Primary_ = &Scalar{
|
2023-01-16 11:52:37 +00:00
|
|
|
parent: f,
|
2023-01-18 10:06:44 +00:00
|
|
|
Value: refctx.Key.Value.ScalarBox().Unbox(),
|
2023-01-16 11:52:37 +00:00
|
|
|
}
|
2023-08-13 17:15:13 +00:00
|
|
|
// If the link is a board, we need to transform it into an absolute path.
|
|
|
|
|
if f.Name == "link" {
|
|
|
|
|
c.compileLink(f, refctx)
|
|
|
|
|
}
|
2023-01-16 11:52:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-17 21:10:09 +00:00
|
|
|
// Whether the current lazy glob being applied should not override the field
|
|
|
|
|
// if already set by a non glob key.
|
|
|
|
|
func (c *compiler) ignoreLazyGlob(n Node) bool {
|
|
|
|
|
if c.lazyGlobBeingApplied && n.Primary() != nil {
|
2023-10-31 19:26:01 +00:00
|
|
|
lastPrimaryRef := n.LastPrimaryRef()
|
|
|
|
|
if lastPrimaryRef != nil && !lastPrimaryRef.DueToLazyGlob() {
|
2023-08-17 21:10:09 +00:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-07 05:13:49 +00:00
|
|
|
func (c *compiler) updateLinks(m *Map) {
|
|
|
|
|
for _, f := range m.Fields {
|
|
|
|
|
if f.Name == "link" {
|
2023-07-27 06:10:05 +00:00
|
|
|
val := f.Primary().Value.ScalarString()
|
|
|
|
|
link, err := d2parser.ParseKey(val)
|
|
|
|
|
if err != nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
linkIDA := link.IDA()
|
|
|
|
|
if len(linkIDA) == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// When updateLinks is called, all valid board links are already compiled and changed to the qualified path beginning with "root"
|
|
|
|
|
if linkIDA[0] != "root" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2023-06-07 05:13:49 +00:00
|
|
|
bida := BoardIDA(f)
|
|
|
|
|
aida := IDA(f)
|
|
|
|
|
if len(bida) != len(aida) {
|
|
|
|
|
prependIDA := aida[:len(aida)-len(bida)]
|
2023-09-14 14:22:34 +00:00
|
|
|
fullIDA := []string{"root"}
|
|
|
|
|
// With nested imports, a value may already have been updated with part of the absolute path
|
|
|
|
|
// E.g.,
|
|
|
|
|
// The import prepends path a b c
|
|
|
|
|
// The existing path is b c d
|
|
|
|
|
// So the new path is
|
|
|
|
|
// a b c
|
|
|
|
|
// b c d
|
|
|
|
|
// -------
|
|
|
|
|
// a b c d
|
|
|
|
|
OUTER:
|
|
|
|
|
for i := 1; i < len(prependIDA); i += 2 {
|
|
|
|
|
for j := 0; i+j < len(prependIDA); j++ {
|
|
|
|
|
if prependIDA[i+j] != linkIDA[1+j] {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
// Reached the end and all common
|
|
|
|
|
if i+j == len(prependIDA)-1 {
|
|
|
|
|
break OUTER
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fullIDA = append(fullIDA, prependIDA[i])
|
|
|
|
|
fullIDA = append(fullIDA, prependIDA[i+1])
|
|
|
|
|
}
|
|
|
|
|
// Chop off "root"
|
|
|
|
|
fullIDA = append(fullIDA, linkIDA[1:]...)
|
|
|
|
|
|
|
|
|
|
kp := d2ast.MakeKeyPath(fullIDA)
|
|
|
|
|
s := d2format.Format(kp)
|
2023-06-07 05:37:56 +00:00
|
|
|
f.Primary_.Value = d2ast.MakeValueBox(d2ast.FlatUnquotedString(s)).ScalarBox().Unbox()
|
2023-06-07 05:13:49 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if f.Map() != nil {
|
|
|
|
|
c.updateLinks(f.Map())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-13 17:15:13 +00:00
|
|
|
func (c *compiler) compileLink(f *Field, refctx *RefContext) {
|
2023-03-01 23:38:02 +00:00
|
|
|
val := refctx.Key.Value.ScalarBox().Unbox().ScalarString()
|
|
|
|
|
link, err := d2parser.ParseKey(val)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-03 01:56:32 +00:00
|
|
|
scopeIDA := IDA(refctx.ScopeMap)
|
2023-03-01 23:38:02 +00:00
|
|
|
|
|
|
|
|
if len(scopeIDA) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
linkIDA := link.IDA()
|
|
|
|
|
if len(linkIDA) == 0 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-03 02:01:51 +00:00
|
|
|
if linkIDA[0] == "root" {
|
|
|
|
|
c.errorf(refctx.Key.Key, "cannot refer to root in link")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-03 01:56:32 +00:00
|
|
|
// If it doesn't start with one of these reserved words, the link is definitely not a board link.
|
2023-03-05 17:43:42 +00:00
|
|
|
if !strings.EqualFold(linkIDA[0], "layers") && !strings.EqualFold(linkIDA[0], "scenarios") && !strings.EqualFold(linkIDA[0], "steps") && linkIDA[0] != "_" {
|
2023-03-01 23:38:02 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Chop off the non-board portion of the scope, like if this is being defined on a nested object (e.g. `x.y.z`)
|
|
|
|
|
for i := len(scopeIDA) - 1; i > 0; i-- {
|
2023-03-05 17:43:42 +00:00
|
|
|
if strings.EqualFold(scopeIDA[i-1], "layers") || strings.EqualFold(scopeIDA[i-1], "scenarios") || strings.EqualFold(scopeIDA[i-1], "steps") {
|
2023-03-01 23:38:02 +00:00
|
|
|
scopeIDA = scopeIDA[:i+1]
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
if scopeIDA[i-1] == "root" {
|
|
|
|
|
scopeIDA = scopeIDA[:i]
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resolve underscores
|
|
|
|
|
for len(linkIDA) > 0 && linkIDA[0] == "_" {
|
|
|
|
|
if len(scopeIDA) < 2 {
|
2023-03-01 23:49:47 +00:00
|
|
|
// IR compiler only validates bad underscore usage
|
|
|
|
|
// The compiler will validate if the target board actually exists
|
2023-03-03 01:56:32 +00:00
|
|
|
c.errorf(refctx.Key.Key, "invalid underscore usage")
|
2023-03-01 23:38:02 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// pop 2 off path per one underscore
|
|
|
|
|
scopeIDA = scopeIDA[:len(scopeIDA)-2]
|
|
|
|
|
linkIDA = linkIDA[1:]
|
|
|
|
|
}
|
|
|
|
|
if len(scopeIDA) == 0 {
|
|
|
|
|
scopeIDA = []string{"root"}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create the absolute path by appending scope path with value specified
|
|
|
|
|
scopeIDA = append(scopeIDA, linkIDA...)
|
2023-03-03 02:25:14 +00:00
|
|
|
kp := d2ast.MakeKeyPath(scopeIDA)
|
2023-08-13 17:15:13 +00:00
|
|
|
f.Primary_.Value = d2ast.FlatUnquotedString(d2format.Format(kp))
|
2023-03-01 23:38:02 +00:00
|
|
|
}
|
|
|
|
|
|
2023-01-24 05:48:43 +00:00
|
|
|
func (c *compiler) compileEdges(refctx *RefContext) {
|
2023-06-24 22:31:58 +00:00
|
|
|
if refctx.Key.Key == nil {
|
|
|
|
|
c._compileEdges(refctx)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-30 20:52:42 +00:00
|
|
|
fa, err := refctx.ScopeMap.EnsureField(refctx.Key.Key, refctx, true, c)
|
2023-06-24 22:31:58 +00:00
|
|
|
if err != nil {
|
|
|
|
|
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
for _, f := range fa {
|
2023-01-18 05:28:33 +00:00
|
|
|
if _, ok := f.Composite.(*Array); ok {
|
|
|
|
|
c.errorf(refctx.Key.Key, "cannot index into array")
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-01-18 15:15:16 +00:00
|
|
|
if f.Map() == nil {
|
|
|
|
|
f.Composite = &Map{
|
2023-01-16 11:52:37 +00:00
|
|
|
parent: f,
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-06-24 22:31:58 +00:00
|
|
|
refctx2 := *refctx
|
|
|
|
|
refctx2.ScopeMap = f.Map()
|
|
|
|
|
c._compileEdges(&refctx2)
|
2023-01-16 11:52:37 +00:00
|
|
|
}
|
2023-06-24 22:31:58 +00:00
|
|
|
}
|
2023-01-16 11:52:37 +00:00
|
|
|
|
2023-06-24 22:31:58 +00:00
|
|
|
func (c *compiler) _compileEdges(refctx *RefContext) {
|
2023-01-18 05:28:33 +00:00
|
|
|
eida := NewEdgeIDs(refctx.Key)
|
2023-01-16 11:52:37 +00:00
|
|
|
for i, eid := range eida {
|
2024-06-03 19:11:45 +00:00
|
|
|
if !eid.Glob && (refctx.Key.Primary.Null != nil || refctx.Key.Value.Null != nil) {
|
2023-06-26 18:57:18 +00:00
|
|
|
refctx.ScopeMap.DeleteEdge(eid)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-18 10:06:44 +00:00
|
|
|
refctx = refctx.Copy()
|
|
|
|
|
refctx.Edge = refctx.Key.Edges[i]
|
|
|
|
|
|
2023-06-24 22:31:58 +00:00
|
|
|
var ea []*Edge
|
2023-07-27 09:51:43 +00:00
|
|
|
if eid.Index != nil || eid.Glob {
|
2023-08-17 02:17:41 +00:00
|
|
|
ea = refctx.ScopeMap.GetEdges(eid, refctx, c)
|
2023-01-16 11:52:37 +00:00
|
|
|
if len(ea) == 0 {
|
2023-07-30 20:52:42 +00:00
|
|
|
if !eid.Glob {
|
|
|
|
|
c.errorf(refctx.Edge, "indexed edge does not exist")
|
|
|
|
|
}
|
2023-01-16 11:52:37 +00:00
|
|
|
continue
|
|
|
|
|
}
|
2023-06-24 22:31:58 +00:00
|
|
|
for _, e := range ea {
|
|
|
|
|
e.References = append(e.References, &EdgeReference{
|
2023-08-20 04:20:32 +00:00
|
|
|
Context_: refctx,
|
|
|
|
|
DueToGlob_: len(c.globRefContextStack) > 0,
|
|
|
|
|
DueToLazyGlob_: c.lazyGlobBeingApplied,
|
2023-06-24 22:31:58 +00:00
|
|
|
})
|
2023-08-17 21:10:09 +00:00
|
|
|
refctx.ScopeMap.appendFieldReferences(0, refctx.Edge.Src, refctx, c)
|
|
|
|
|
refctx.ScopeMap.appendFieldReferences(0, refctx.Edge.Dst, refctx, c)
|
2024-06-03 19:11:45 +00:00
|
|
|
|
|
|
|
|
if refctx.Key.Primary.Null != nil || refctx.Key.Value.Null != nil {
|
|
|
|
|
refctx.ScopeMap.DeleteEdge(e.ID)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2023-06-24 22:31:58 +00:00
|
|
|
}
|
2023-01-16 11:52:37 +00:00
|
|
|
} else {
|
2023-07-30 19:41:15 +00:00
|
|
|
var err error
|
2023-07-30 20:52:42 +00:00
|
|
|
ea, err = refctx.ScopeMap.CreateEdge(eid, refctx, c)
|
2023-01-16 11:52:37 +00:00
|
|
|
if err != nil {
|
2023-01-18 10:06:44 +00:00
|
|
|
c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
|
2023-01-16 11:52:37 +00:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-24 22:31:58 +00:00
|
|
|
for _, e := range ea {
|
|
|
|
|
if refctx.Key.EdgeKey != nil {
|
2023-01-18 15:15:16 +00:00
|
|
|
if e.Map_ == nil {
|
|
|
|
|
e.Map_ = &Map{
|
2023-01-16 11:52:37 +00:00
|
|
|
parent: e,
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-29 21:58:48 +00:00
|
|
|
c.compileField(e.Map_, refctx.Key.EdgeKey, refctx)
|
2023-06-24 22:31:58 +00:00
|
|
|
} else {
|
|
|
|
|
if refctx.Key.Primary.Unbox() != nil {
|
2023-08-17 21:10:09 +00:00
|
|
|
if c.ignoreLazyGlob(e) {
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-06-24 22:31:58 +00:00
|
|
|
e.Primary_ = &Scalar{
|
|
|
|
|
parent: e,
|
|
|
|
|
Value: refctx.Key.Primary.Unbox(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if refctx.Key.Value.Array != nil {
|
|
|
|
|
c.errorf(refctx.Key.Value.Unbox(), "edges cannot be assigned arrays")
|
|
|
|
|
continue
|
|
|
|
|
} else if refctx.Key.Value.Map != nil {
|
|
|
|
|
if e.Map_ == nil {
|
|
|
|
|
e.Map_ = &Map{
|
|
|
|
|
parent: e,
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-30 20:52:42 +00:00
|
|
|
c.mapRefContextStack = append(c.mapRefContextStack, refctx)
|
2023-06-24 22:31:58 +00:00
|
|
|
c.compileMap(e.Map_, refctx.Key.Value.Map, refctx.ScopeAST)
|
2023-07-30 20:52:42 +00:00
|
|
|
c.mapRefContextStack = c.mapRefContextStack[:len(c.mapRefContextStack)-1]
|
2023-06-24 22:31:58 +00:00
|
|
|
} else if refctx.Key.Value.ScalarBox().Unbox() != nil {
|
2023-08-17 21:10:09 +00:00
|
|
|
if c.ignoreLazyGlob(e) {
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-06-24 22:31:58 +00:00
|
|
|
e.Primary_ = &Scalar{
|
|
|
|
|
parent: e,
|
|
|
|
|
Value: refctx.Key.Value.ScalarBox().Unbox(),
|
|
|
|
|
}
|
2023-01-22 09:43:25 +00:00
|
|
|
}
|
2023-01-16 11:52:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-06-20 03:06:26 +00:00
|
|
|
func (c *compiler) compileArray(dst *Array, a *d2ast.Array, scopeAST *d2ast.Map) {
|
2023-01-16 12:48:45 +00:00
|
|
|
for _, an := range a.Nodes {
|
|
|
|
|
var irv Value
|
|
|
|
|
switch v := an.Unbox().(type) {
|
|
|
|
|
case *d2ast.Array:
|
|
|
|
|
ira := &Array{
|
|
|
|
|
parent: dst,
|
|
|
|
|
}
|
2023-06-20 03:06:26 +00:00
|
|
|
c.compileArray(ira, v, scopeAST)
|
2023-01-16 12:48:45 +00:00
|
|
|
irv = ira
|
|
|
|
|
case *d2ast.Map:
|
|
|
|
|
irm := &Map{
|
|
|
|
|
parent: dst,
|
|
|
|
|
}
|
2023-06-20 03:06:26 +00:00
|
|
|
c.compileMap(irm, v, scopeAST)
|
2023-01-16 12:48:45 +00:00
|
|
|
irv = irm
|
|
|
|
|
case d2ast.Scalar:
|
|
|
|
|
irv = &Scalar{
|
|
|
|
|
parent: dst,
|
|
|
|
|
Value: v,
|
|
|
|
|
}
|
2023-06-04 22:39:00 +00:00
|
|
|
case *d2ast.Import:
|
|
|
|
|
n, ok := c._import(v)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
switch n := n.(type) {
|
|
|
|
|
case *Field:
|
|
|
|
|
if v.Spread {
|
|
|
|
|
a, ok := n.Composite.(*Array)
|
|
|
|
|
if !ok {
|
|
|
|
|
c.errorf(v, "can only spread import array into array")
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
dst.Values = append(dst.Values, a.Values...)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if n.Composite != nil {
|
|
|
|
|
irv = n.Composite
|
|
|
|
|
} else {
|
|
|
|
|
irv = n.Primary_
|
|
|
|
|
}
|
|
|
|
|
case *Map:
|
|
|
|
|
if v.Spread {
|
2023-06-06 23:49:12 +00:00
|
|
|
c.errorf(v, "can only spread import array into array")
|
2023-06-04 22:39:00 +00:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
irv = n
|
|
|
|
|
}
|
2023-01-16 15:45:13 +00:00
|
|
|
case *d2ast.Substitution:
|
2023-07-13 05:20:55 +00:00
|
|
|
irv = &Scalar{
|
|
|
|
|
parent: dst,
|
|
|
|
|
Value: &d2ast.UnquotedString{
|
|
|
|
|
Value: []d2ast.InterpolationBox{{Substitution: an.Substitution}},
|
|
|
|
|
},
|
|
|
|
|
}
|
2023-01-16 12:48:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dst.Values = append(dst.Values, irv)
|
|
|
|
|
}
|
2023-01-16 11:52:37 +00:00
|
|
|
}
|