...

Source file src/oss.terrastruct.com/d2/d2ir/compile.go

Documentation: oss.terrastruct.com/d2/d2ir

     1  package d2ir
     2  
     3  import (
     4  	"io/fs"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"oss.terrastruct.com/util-go/go2"
     9  
    10  	"oss.terrastruct.com/d2/d2ast"
    11  	"oss.terrastruct.com/d2/d2format"
    12  	"oss.terrastruct.com/d2/d2parser"
    13  	"oss.terrastruct.com/d2/d2themes"
    14  	"oss.terrastruct.com/d2/d2themes/d2themescatalog"
    15  )
    16  
    17  type globContext struct {
    18  	root   *globContext
    19  	refctx *RefContext
    20  
    21  	// Set of BoardIDA that this glob has already applied to.
    22  	appliedFields map[string]struct{}
    23  	// Set of Edge IDs that this glob has already applied to.
    24  	appliedEdges map[string]struct{}
    25  }
    26  
    27  type compiler struct {
    28  	err *d2parser.ParseError
    29  
    30  	fs fs.FS
    31  	// importStack is used to detect cyclic imports.
    32  	importStack []string
    33  	// importCache enables reuse of files imported multiple times.
    34  	importCache map[string]*Map
    35  	utf16Pos    bool
    36  
    37  	// Stack of globs that must be recomputed at each new object in and below the current scope.
    38  	globContextStack [][]*globContext
    39  	// Used to prevent field globs causing infinite loops.
    40  	globRefContextStack []*RefContext
    41  	// Used to check whether ampersands are allowed in the current map.
    42  	mapRefContextStack   []*RefContext
    43  	lazyGlobBeingApplied bool
    44  }
    45  
    46  type CompileOptions struct {
    47  	UTF16Pos bool
    48  	// Pass nil to disable imports.
    49  	FS fs.FS
    50  }
    51  
    52  func (c *compiler) errorf(n d2ast.Node, f string, v ...interface{}) {
    53  	c.err.Errors = append(c.err.Errors, d2parser.Errorf(n, f, v...).(d2ast.Error))
    54  }
    55  
    56  func Compile(ast *d2ast.Map, opts *CompileOptions) (*Map, error) {
    57  	if opts == nil {
    58  		opts = &CompileOptions{}
    59  	}
    60  	c := &compiler{
    61  		err: &d2parser.ParseError{},
    62  		fs:  opts.FS,
    63  
    64  		importCache: make(map[string]*Map),
    65  		utf16Pos:    opts.UTF16Pos,
    66  	}
    67  	m := &Map{}
    68  	m.initRoot()
    69  	m.parent.(*Field).References[0].Context_.Scope = ast
    70  	m.parent.(*Field).References[0].Context_.ScopeAST = ast
    71  
    72  	c.pushImportStack(&d2ast.Import{
    73  		Path: []*d2ast.StringBox{d2ast.RawStringBox(ast.GetRange().Path, true)},
    74  	})
    75  	defer c.popImportStack()
    76  
    77  	c.compileMap(m, ast, ast)
    78  	c.compileSubstitutions(m, nil)
    79  	c.overlayClasses(m)
    80  	if !c.err.Empty() {
    81  		return nil, c.err
    82  	}
    83  	return m, nil
    84  }
    85  
    86  func (c *compiler) overlayClasses(m *Map) {
    87  	classes := m.GetField("classes")
    88  	if classes == nil || classes.Map() == nil {
    89  		return
    90  	}
    91  
    92  	layersField := m.GetField("layers")
    93  	if layersField == nil {
    94  		return
    95  	}
    96  	layers := layersField.Map()
    97  	if layers == nil {
    98  		return
    99  	}
   100  
   101  	for _, lf := range layers.Fields {
   102  		if lf.Map() == nil || lf.Primary() != nil {
   103  			c.errorf(lf.References[0].Context_.Key, "invalid layer")
   104  			continue
   105  		}
   106  		l := lf.Map()
   107  		lClasses := l.GetField("classes")
   108  
   109  		if lClasses == nil {
   110  			lClasses = classes.Copy(l).(*Field)
   111  			l.Fields = append(l.Fields, lClasses)
   112  		} else {
   113  			base := classes.Copy(l).(*Field)
   114  			OverlayMap(base.Map(), lClasses.Map())
   115  			l.DeleteField("classes")
   116  			l.Fields = append(l.Fields, base)
   117  		}
   118  
   119  		c.overlayClasses(l)
   120  	}
   121  }
   122  
   123  func (c *compiler) compileSubstitutions(m *Map, varsStack []*Map) {
   124  	for _, f := range m.Fields {
   125  		if f.Name == "vars" && f.Map() != nil {
   126  			varsStack = append([]*Map{f.Map()}, varsStack...)
   127  		}
   128  	}
   129  	for _, f := range m.Fields {
   130  		if f.Primary() != nil {
   131  			c.resolveSubstitutions(varsStack, f)
   132  		}
   133  		if arr, ok := f.Composite.(*Array); ok {
   134  			for _, val := range arr.Values {
   135  				if scalar, ok := val.(*Scalar); ok {
   136  					c.resolveSubstitutions(varsStack, scalar)
   137  				}
   138  			}
   139  		} else if f.Map() != nil {
   140  			// don't resolve substitutions in vars with the current scope of vars
   141  			if f.Name == "vars" {
   142  				c.compileSubstitutions(f.Map(), varsStack[1:])
   143  				c.validateConfigs(f.Map().GetField("d2-config"))
   144  			} else {
   145  				c.compileSubstitutions(f.Map(), varsStack)
   146  			}
   147  		}
   148  	}
   149  	for _, e := range m.Edges {
   150  		if e.Primary() != nil {
   151  			c.resolveSubstitutions(varsStack, e)
   152  		}
   153  		if e.Map() != nil {
   154  			c.compileSubstitutions(e.Map(), varsStack)
   155  		}
   156  	}
   157  }
   158  
   159  func (c *compiler) validateConfigs(configs *Field) {
   160  	if configs == nil || configs.Map() == nil {
   161  		return
   162  	}
   163  
   164  	if NodeBoardKind(ParentMap(ParentMap(configs))) == "" {
   165  		c.errorf(configs.LastRef().AST(), `"%s" can only appear at root vars`, configs.Name)
   166  		return
   167  	}
   168  
   169  	for _, f := range configs.Map().Fields {
   170  		var val string
   171  		if f.Primary() == nil {
   172  			if f.Name != "theme-overrides" && f.Name != "dark-theme-overrides" {
   173  				c.errorf(f.LastRef().AST(), `"%s" needs a value`, f.Name)
   174  				continue
   175  			}
   176  		} else {
   177  			val = f.Primary().Value.ScalarString()
   178  		}
   179  
   180  		switch f.Name {
   181  		case "sketch", "center":
   182  			_, err := strconv.ParseBool(val)
   183  			if err != nil {
   184  				c.errorf(f.LastRef().AST(), `expected a boolean for "%s", got "%s"`, f.Name, val)
   185  				continue
   186  			}
   187  		case "theme-overrides", "dark-theme-overrides":
   188  			if f.Map() == nil {
   189  				c.errorf(f.LastRef().AST(), `"%s" needs a map`, f.Name)
   190  				continue
   191  			}
   192  		case "theme-id", "dark-theme-id":
   193  			valInt, err := strconv.Atoi(val)
   194  			if err != nil {
   195  				c.errorf(f.LastRef().AST(), `expected an integer for "%s", got "%s"`, f.Name, val)
   196  				continue
   197  			}
   198  			if d2themescatalog.Find(int64(valInt)) == (d2themes.Theme{}) {
   199  				c.errorf(f.LastRef().AST(), `%d is not a valid theme ID`, valInt)
   200  				continue
   201  			}
   202  		case "pad":
   203  			_, err := strconv.Atoi(val)
   204  			if err != nil {
   205  				c.errorf(f.LastRef().AST(), `expected an integer for "%s", got "%s"`, f.Name, val)
   206  				continue
   207  			}
   208  		case "layout-engine":
   209  		default:
   210  			c.errorf(f.LastRef().AST(), `"%s" is not a valid config`, f.Name)
   211  		}
   212  	}
   213  }
   214  
   215  func (c *compiler) resolveSubstitutions(varsStack []*Map, node Node) {
   216  	var subbed bool
   217  	var resolvedField *Field
   218  
   219  	switch s := node.Primary().Value.(type) {
   220  	case *d2ast.UnquotedString:
   221  		for i, box := range s.Value {
   222  			if box.Substitution != nil {
   223  				for _, vars := range varsStack {
   224  					resolvedField = c.resolveSubstitution(vars, box.Substitution)
   225  					if resolvedField != nil {
   226  						if resolvedField.Primary() != nil {
   227  							if _, ok := resolvedField.Primary().Value.(*d2ast.Null); ok {
   228  								resolvedField = nil
   229  							}
   230  						}
   231  						break
   232  					}
   233  				}
   234  				if resolvedField == nil {
   235  					c.errorf(node.LastRef().AST(), `could not resolve variable "%s"`, strings.Join(box.Substitution.IDA(), "."))
   236  					return
   237  				}
   238  				if box.Substitution.Spread {
   239  					if resolvedField.Composite == nil {
   240  						c.errorf(box.Substitution, "cannot spread non-composite")
   241  						continue
   242  					}
   243  					switch n := node.(type) {
   244  					case *Scalar: // Array value
   245  						resolvedArr, ok := resolvedField.Composite.(*Array)
   246  						if !ok {
   247  							c.errorf(box.Substitution, "cannot spread non-array into array")
   248  							continue
   249  						}
   250  						arr := n.parent.(*Array)
   251  						for i, s := range arr.Values {
   252  							if s == n {
   253  								arr.Values = append(append(arr.Values[:i], resolvedArr.Values...), arr.Values[i+1:]...)
   254  								break
   255  							}
   256  						}
   257  					case *Field:
   258  						if resolvedField.Map() != nil {
   259  							OverlayMap(ParentMap(n), resolvedField.Map())
   260  						}
   261  						// Remove the placeholder field
   262  						m := n.parent.(*Map)
   263  						for i, f2 := range m.Fields {
   264  							if n == f2 {
   265  								m.Fields = append(m.Fields[:i], m.Fields[i+1:]...)
   266  								break
   267  							}
   268  						}
   269  					}
   270  				}
   271  				if resolvedField.Primary() == nil {
   272  					if resolvedField.Composite == nil {
   273  						c.errorf(node.LastRef().AST(), `cannot substitute variable without value: "%s"`, strings.Join(box.Substitution.IDA(), "."))
   274  						return
   275  					}
   276  					if len(s.Value) > 1 {
   277  						c.errorf(node.LastRef().AST(), `cannot substitute composite variable "%s" as part of a string`, strings.Join(box.Substitution.IDA(), "."))
   278  						return
   279  					}
   280  					switch n := node.(type) {
   281  					case *Field:
   282  						n.Primary_ = nil
   283  					case *Edge:
   284  						n.Primary_ = nil
   285  					}
   286  				} else {
   287  					if i == 0 && len(s.Value) == 1 {
   288  						node.Primary().Value = resolvedField.Primary().Value
   289  					} else {
   290  						s.Value[i].String = go2.Pointer(resolvedField.Primary().Value.ScalarString())
   291  						subbed = true
   292  					}
   293  				}
   294  				if resolvedField.Composite != nil {
   295  					switch n := node.(type) {
   296  					case *Field:
   297  						n.Composite = resolvedField.Composite
   298  					case *Edge:
   299  						if resolvedField.Composite.Map() == nil {
   300  							c.errorf(node.LastRef().AST(), `cannot substitute array variable "%s" to an edge`, strings.Join(box.Substitution.IDA(), "."))
   301  							return
   302  						}
   303  						n.Map_ = resolvedField.Composite.Map()
   304  					}
   305  				}
   306  			}
   307  		}
   308  		if subbed {
   309  			s.Coalesce()
   310  		}
   311  	case *d2ast.DoubleQuotedString:
   312  		for i, box := range s.Value {
   313  			if box.Substitution != nil {
   314  				for _, vars := range varsStack {
   315  					resolvedField = c.resolveSubstitution(vars, box.Substitution)
   316  					if resolvedField != nil {
   317  						break
   318  					}
   319  				}
   320  				if resolvedField == nil {
   321  					c.errorf(node.LastRef().AST(), `could not resolve variable "%s"`, strings.Join(box.Substitution.IDA(), "."))
   322  					return
   323  				}
   324  				if resolvedField.Primary() == nil && resolvedField.Composite != nil {
   325  					c.errorf(node.LastRef().AST(), `cannot substitute map variable "%s" in quotes`, strings.Join(box.Substitution.IDA(), "."))
   326  					return
   327  				}
   328  				s.Value[i].String = go2.Pointer(resolvedField.Primary().Value.ScalarString())
   329  				subbed = true
   330  			}
   331  		}
   332  		if subbed {
   333  			s.Coalesce()
   334  		}
   335  	}
   336  }
   337  
   338  func (c *compiler) resolveSubstitution(vars *Map, substitution *d2ast.Substitution) *Field {
   339  	if vars == nil {
   340  		return nil
   341  	}
   342  
   343  	for i, p := range substitution.Path {
   344  		f := vars.GetField(p.Unbox().ScalarString())
   345  		if f == nil {
   346  			return nil
   347  		}
   348  
   349  		if i == len(substitution.Path)-1 {
   350  			return f
   351  		}
   352  		vars = f.Map()
   353  	}
   354  	return nil
   355  }
   356  
   357  func (c *compiler) overlay(base *Map, f *Field) {
   358  	if f.Map() == nil || f.Primary() != nil {
   359  		c.errorf(f.References[0].Context_.Key, "invalid %s", NodeBoardKind(f))
   360  		return
   361  	}
   362  	base = base.CopyBase(f)
   363  	OverlayMap(base, f.Map())
   364  	f.Composite = base
   365  }
   366  
   367  func (g *globContext) copy() *globContext {
   368  	g2 := *g
   369  	g2.refctx = g.root.refctx.Copy()
   370  	return &g2
   371  }
   372  
   373  func (g *globContext) prefixed(dst *Map) *globContext {
   374  	g2 := g.copy()
   375  	prefix := d2ast.MakeKeyPath(RelIDA(g2.refctx.ScopeMap, dst))
   376  	g2.refctx.Key = g2.refctx.Key.Copy()
   377  	if g2.refctx.Key.Key != nil {
   378  		prefix.Path = append(prefix.Path, g2.refctx.Key.Key.Path...)
   379  	}
   380  	if len(prefix.Path) > 0 {
   381  		g2.refctx.Key.Key = prefix
   382  	}
   383  	return g2
   384  }
   385  
   386  func (c *compiler) ampersandFilterMap(dst *Map, ast, scopeAST *d2ast.Map) bool {
   387  	for _, n := range ast.Nodes {
   388  		switch {
   389  		case n.MapKey != nil:
   390  			ok := c.ampersandFilter(&RefContext{
   391  				Key:      n.MapKey,
   392  				Scope:    ast,
   393  				ScopeMap: dst,
   394  				ScopeAST: scopeAST,
   395  			})
   396  			if !ok {
   397  				if len(c.mapRefContextStack) == 0 {
   398  					return false
   399  				}
   400  				// Unapply glob if appropriate.
   401  				gctx := c.getGlobContext(c.mapRefContextStack[len(c.mapRefContextStack)-1])
   402  				if gctx == nil {
   403  					return false
   404  				}
   405  				var ks string
   406  				if gctx.refctx.Key.HasTripleGlob() {
   407  					ks = d2format.Format(d2ast.MakeKeyPath(IDA(dst)))
   408  				} else {
   409  					ks = d2format.Format(d2ast.MakeKeyPath(BoardIDA(dst)))
   410  				}
   411  				delete(gctx.appliedFields, ks)
   412  				return false
   413  			}
   414  		}
   415  	}
   416  	return true
   417  }
   418  
   419  func (c *compiler) compileMap(dst *Map, ast, scopeAST *d2ast.Map) {
   420  	var globs []*globContext
   421  	if len(c.globContextStack) > 0 {
   422  		previousGlobs := c.globContexts()
   423  		// A root layer with existing glob context stack implies it's an import
   424  		// In which case, the previous globs should be inherited (the else block)
   425  		if NodeBoardKind(dst) == BoardLayer && !dst.Root() {
   426  			for _, g := range previousGlobs {
   427  				if g.refctx.Key.HasTripleGlob() {
   428  					globs = append(globs, g.prefixed(dst))
   429  				}
   430  			}
   431  		} else if NodeBoardKind(dst) != "" {
   432  			// Make all globs relative to the scenario or step.
   433  			for _, g := range previousGlobs {
   434  				globs = append(globs, g.prefixed(dst))
   435  			}
   436  		} else {
   437  			globs = append(globs, previousGlobs...)
   438  		}
   439  	}
   440  	c.globContextStack = append(c.globContextStack, globs)
   441  	defer func() {
   442  		dst.globs = c.globContexts()
   443  		c.globContextStack = c.globContextStack[:len(c.globContextStack)-1]
   444  	}()
   445  
   446  	ok := c.ampersandFilterMap(dst, ast, scopeAST)
   447  	if !ok {
   448  		return
   449  	}
   450  
   451  	for _, n := range ast.Nodes {
   452  		switch {
   453  		case n.MapKey != nil:
   454  			c.compileKey(&RefContext{
   455  				Key:      n.MapKey,
   456  				Scope:    ast,
   457  				ScopeMap: dst,
   458  				ScopeAST: scopeAST,
   459  			})
   460  		case n.Substitution != nil:
   461  			// placeholder field to be resolved at the end
   462  			f := &Field{
   463  				parent: dst,
   464  				Primary_: &Scalar{
   465  					Value: &d2ast.UnquotedString{
   466  						Value: []d2ast.InterpolationBox{{Substitution: n.Substitution}},
   467  					},
   468  				},
   469  				References: []*FieldReference{{
   470  					Context_: &RefContext{
   471  						Scope:    ast,
   472  						ScopeMap: dst,
   473  						ScopeAST: scopeAST,
   474  					},
   475  				}},
   476  			}
   477  			dst.Fields = append(dst.Fields, f)
   478  		case n.Import != nil:
   479  			impn, ok := c._import(n.Import)
   480  			if !ok {
   481  				continue
   482  			}
   483  			if impn.Map() == nil {
   484  				c.errorf(n.Import, "cannot spread import non map into map")
   485  				continue
   486  			}
   487  
   488  			for _, gctx := range impn.Map().globs {
   489  				if !gctx.refctx.Key.HasTripleGlob() {
   490  					continue
   491  				}
   492  				gctx2 := gctx.copy()
   493  				gctx2.refctx.ScopeMap = dst
   494  				c.compileKey(gctx2.refctx)
   495  				c.ensureGlobContext(gctx2.refctx)
   496  			}
   497  
   498  			OverlayMap(dst, impn.Map())
   499  
   500  			if impnf, ok := impn.(*Field); ok {
   501  				if impnf.Primary_ != nil {
   502  					dstf := ParentField(dst)
   503  					if dstf != nil {
   504  						dstf.Primary_ = impnf.Primary_
   505  					}
   506  				}
   507  			}
   508  		}
   509  	}
   510  }
   511  
   512  func (c *compiler) globContexts() []*globContext {
   513  	return c.globContextStack[len(c.globContextStack)-1]
   514  }
   515  
   516  func (c *compiler) getGlobContext(refctx *RefContext) *globContext {
   517  	for _, gctx := range c.globContexts() {
   518  		if gctx.refctx.Equal(refctx) {
   519  			return gctx
   520  		}
   521  	}
   522  	return nil
   523  }
   524  
   525  func (c *compiler) ensureGlobContext(refctx *RefContext) *globContext {
   526  	gctx := c.getGlobContext(refctx)
   527  	if gctx != nil {
   528  		return gctx
   529  	}
   530  	gctx = &globContext{
   531  		refctx:        refctx,
   532  		appliedFields: make(map[string]struct{}),
   533  		appliedEdges:  make(map[string]struct{}),
   534  	}
   535  	gctx.root = gctx
   536  	c.globContextStack[len(c.globContextStack)-1] = append(c.globContexts(), gctx)
   537  	return gctx
   538  }
   539  
   540  func (c *compiler) compileKey(refctx *RefContext) {
   541  	if refctx.Key.HasGlob() {
   542  		// These printlns are for debugging infinite loops.
   543  		// println("og", refctx.Edge, refctx.Key, refctx.Scope, refctx.ScopeMap, refctx.ScopeAST)
   544  		for _, refctx2 := range c.globRefContextStack {
   545  			// println("st", refctx2.Edge, refctx2.Key, refctx2.Scope, refctx2.ScopeMap, refctx2.ScopeAST)
   546  			if refctx.Equal(refctx2) {
   547  				// Break the infinite loop.
   548  				return
   549  			}
   550  			// println("keys", d2format.Format(refctx2.Key), d2format.Format(refctx.Key))
   551  		}
   552  		c.globRefContextStack = append(c.globRefContextStack, refctx)
   553  		defer func() {
   554  			c.globRefContextStack = c.globRefContextStack[:len(c.globRefContextStack)-1]
   555  		}()
   556  		c.ensureGlobContext(refctx)
   557  	}
   558  	oldFields := refctx.ScopeMap.FieldCountRecursive()
   559  	oldEdges := refctx.ScopeMap.EdgeCountRecursive()
   560  	if len(refctx.Key.Edges) == 0 {
   561  		c.compileField(refctx.ScopeMap, refctx.Key.Key, refctx)
   562  	} else {
   563  		c.compileEdges(refctx)
   564  	}
   565  	if oldFields != refctx.ScopeMap.FieldCountRecursive() || oldEdges != refctx.ScopeMap.EdgeCountRecursive() {
   566  		for _, gctx2 := range c.globContexts() {
   567  			// println(d2format.Format(gctx2.refctx.Key), d2format.Format(refctx.Key))
   568  			old := c.lazyGlobBeingApplied
   569  			c.lazyGlobBeingApplied = true
   570  			c.compileKey(gctx2.refctx)
   571  			c.lazyGlobBeingApplied = old
   572  		}
   573  	}
   574  }
   575  
   576  func (c *compiler) compileField(dst *Map, kp *d2ast.KeyPath, refctx *RefContext) {
   577  	if refctx.Key.Ampersand {
   578  		return
   579  	}
   580  
   581  	fa, err := dst.EnsureField(kp, refctx, true, c)
   582  	if err != nil {
   583  		c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
   584  		return
   585  	}
   586  
   587  	for _, f := range fa {
   588  		c._compileField(f, refctx)
   589  	}
   590  }
   591  
   592  func (c *compiler) ampersandFilter(refctx *RefContext) bool {
   593  	if !refctx.Key.Ampersand {
   594  		return true
   595  	}
   596  	if len(c.mapRefContextStack) == 0 || !c.mapRefContextStack[len(c.mapRefContextStack)-1].Key.SupportsGlobFilters() {
   597  		c.errorf(refctx.Key, "glob filters cannot be used outside globs")
   598  		return false
   599  	}
   600  	if len(refctx.Key.Edges) > 0 {
   601  		return true
   602  	}
   603  
   604  	fa, err := refctx.ScopeMap.EnsureField(refctx.Key.Key, refctx, false, c)
   605  	if err != nil {
   606  		c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
   607  		return false
   608  	}
   609  	if len(fa) == 0 {
   610  		if refctx.Key.Key.Last().ScalarString() != "label" {
   611  			return false
   612  		}
   613  		kp := refctx.Key.Key.Copy()
   614  		kp.Path = kp.Path[:len(kp.Path)-1]
   615  		if len(kp.Path) == 0 {
   616  			n := refctx.ScopeMap.Parent()
   617  			switch n := n.(type) {
   618  			case *Field:
   619  				fa = append(fa, n)
   620  			case *Edge:
   621  				if n.Primary_ == nil {
   622  					if refctx.Key.Value.ScalarBox().Unbox().ScalarString() == "" {
   623  						return true
   624  					}
   625  					return false
   626  				}
   627  				if n.Primary_.Value.ScalarString() != refctx.Key.Value.ScalarBox().Unbox().ScalarString() {
   628  					return false
   629  				}
   630  			}
   631  		} else {
   632  			fa, err = refctx.ScopeMap.EnsureField(kp, refctx, false, c)
   633  			if err != nil {
   634  				c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
   635  				return false
   636  			}
   637  		}
   638  		for _, f := range fa {
   639  			label := f.Name
   640  			if f.Primary_ != nil {
   641  				label = f.Primary_.Value.ScalarString()
   642  			}
   643  			if label != refctx.Key.Value.ScalarBox().Unbox().ScalarString() {
   644  				return false
   645  			}
   646  		}
   647  		return true
   648  	}
   649  	for _, f := range fa {
   650  		ok := c._ampersandFilter(f, refctx)
   651  		if !ok {
   652  			return false
   653  		}
   654  	}
   655  	return true
   656  }
   657  
   658  func (c *compiler) _ampersandFilter(f *Field, refctx *RefContext) bool {
   659  	if refctx.Key.Value.ScalarBox().Unbox() == nil {
   660  		c.errorf(refctx.Key, "glob filters cannot be composites")
   661  		return false
   662  	}
   663  
   664  	if a, ok := f.Composite.(*Array); ok {
   665  		for _, v := range a.Values {
   666  			if s, ok := v.(*Scalar); ok {
   667  				if refctx.Key.Value.ScalarBox().Unbox().ScalarString() == s.Value.ScalarString() {
   668  					return true
   669  				}
   670  			}
   671  		}
   672  	}
   673  
   674  	if f.Primary_ == nil {
   675  		return false
   676  	}
   677  
   678  	if refctx.Key.Value.ScalarBox().Unbox().ScalarString() != f.Primary_.Value.ScalarString() {
   679  		return false
   680  	}
   681  
   682  	return true
   683  }
   684  
   685  func (c *compiler) _compileField(f *Field, refctx *RefContext) {
   686  	// In case of filters, we need to pass filters before continuing
   687  	if refctx.Key.Value.Map != nil && refctx.Key.Value.Map.HasFilter() {
   688  		if f.Map() == nil {
   689  			f.Composite = &Map{
   690  				parent: f,
   691  			}
   692  		}
   693  		c.mapRefContextStack = append(c.mapRefContextStack, refctx)
   694  		ok := c.ampersandFilterMap(f.Map(), refctx.Key.Value.Map, refctx.ScopeAST)
   695  		c.mapRefContextStack = c.mapRefContextStack[:len(c.mapRefContextStack)-1]
   696  		if !ok {
   697  			return
   698  		}
   699  	}
   700  
   701  	if len(refctx.Key.Edges) == 0 && (refctx.Key.Primary.Null != nil || refctx.Key.Value.Null != nil) {
   702  		// For vars, if we delete the field, it may just resolve to an outer scope var of the same name
   703  		// Instead we keep it around, so that resolveSubstitutions can find it
   704  		if !IsVar(ParentMap(f)) {
   705  			ParentMap(f).DeleteField(f.Name)
   706  			return
   707  		}
   708  	}
   709  
   710  	if refctx.Key.Primary.Unbox() != nil {
   711  		if c.ignoreLazyGlob(f) {
   712  			return
   713  		}
   714  		f.Primary_ = &Scalar{
   715  			parent: f,
   716  			Value:  refctx.Key.Primary.Unbox(),
   717  		}
   718  	}
   719  
   720  	if refctx.Key.Value.Array != nil {
   721  		a := &Array{
   722  			parent: f,
   723  		}
   724  		c.compileArray(a, refctx.Key.Value.Array, refctx.ScopeAST)
   725  		f.Composite = a
   726  	} else if refctx.Key.Value.Map != nil {
   727  		scopeAST := refctx.Key.Value.Map
   728  		if f.Map() == nil {
   729  			f.Composite = &Map{
   730  				parent: f,
   731  			}
   732  			switch NodeBoardKind(f) {
   733  			case BoardScenario:
   734  				c.overlay(ParentBoard(f).Map(), f)
   735  			case BoardStep:
   736  				stepsMap := ParentMap(f)
   737  				for i := range stepsMap.Fields {
   738  					if stepsMap.Fields[i] == f {
   739  						if i == 0 {
   740  							c.overlay(ParentBoard(f).Map(), f)
   741  						} else {
   742  							c.overlay(stepsMap.Fields[i-1].Map(), f)
   743  						}
   744  						break
   745  					}
   746  				}
   747  			case BoardLayer:
   748  			default:
   749  				// If new board type, use that as the new scope AST, otherwise, carry on
   750  				scopeAST = refctx.ScopeAST
   751  			}
   752  		} else {
   753  			scopeAST = refctx.ScopeAST
   754  		}
   755  		c.mapRefContextStack = append(c.mapRefContextStack, refctx)
   756  		c.compileMap(f.Map(), refctx.Key.Value.Map, scopeAST)
   757  		c.mapRefContextStack = c.mapRefContextStack[:len(c.mapRefContextStack)-1]
   758  		switch NodeBoardKind(f) {
   759  		case BoardScenario, BoardStep:
   760  			c.overlayClasses(f.Map())
   761  		}
   762  	} else if refctx.Key.Value.Import != nil {
   763  		n, ok := c._import(refctx.Key.Value.Import)
   764  		if !ok {
   765  			return
   766  		}
   767  		switch n := n.(type) {
   768  		case *Field:
   769  			if n.Primary_ != nil {
   770  				f.Primary_ = n.Primary_.Copy(f).(*Scalar)
   771  			}
   772  			if n.Composite != nil {
   773  				f.Composite = n.Composite.Copy(f).(Composite)
   774  			}
   775  		case *Map:
   776  			f.Composite = &Map{
   777  				parent: f,
   778  			}
   779  			switch NodeBoardKind(f) {
   780  			case BoardScenario:
   781  				c.overlay(ParentBoard(f).Map(), f)
   782  			case BoardStep:
   783  				stepsMap := ParentMap(f)
   784  				for i := range stepsMap.Fields {
   785  					if stepsMap.Fields[i] == f {
   786  						if i == 0 {
   787  							c.overlay(ParentBoard(f).Map(), f)
   788  						} else {
   789  							c.overlay(stepsMap.Fields[i-1].Map(), f)
   790  						}
   791  						break
   792  					}
   793  				}
   794  			}
   795  			OverlayMap(f.Map(), n)
   796  			c.updateLinks(f.Map())
   797  			switch NodeBoardKind(f) {
   798  			case BoardScenario, BoardStep:
   799  				c.overlayClasses(f.Map())
   800  			}
   801  		}
   802  	} else if refctx.Key.Value.ScalarBox().Unbox() != nil {
   803  		if c.ignoreLazyGlob(f) {
   804  			return
   805  		}
   806  		f.Primary_ = &Scalar{
   807  			parent: f,
   808  			Value:  refctx.Key.Value.ScalarBox().Unbox(),
   809  		}
   810  		// If the link is a board, we need to transform it into an absolute path.
   811  		if f.Name == "link" {
   812  			c.compileLink(f, refctx)
   813  		}
   814  	}
   815  }
   816  
   817  // Whether the current lazy glob being applied should not override the field
   818  // if already set by a non glob key.
   819  func (c *compiler) ignoreLazyGlob(n Node) bool {
   820  	if c.lazyGlobBeingApplied && n.Primary() != nil {
   821  		lastPrimaryRef := n.LastPrimaryRef()
   822  		if lastPrimaryRef != nil && !lastPrimaryRef.DueToLazyGlob() {
   823  			return true
   824  		}
   825  	}
   826  	return false
   827  }
   828  
   829  func (c *compiler) updateLinks(m *Map) {
   830  	for _, f := range m.Fields {
   831  		if f.Name == "link" {
   832  			val := f.Primary().Value.ScalarString()
   833  			link, err := d2parser.ParseKey(val)
   834  			if err != nil {
   835  				continue
   836  			}
   837  
   838  			linkIDA := link.IDA()
   839  			if len(linkIDA) == 0 {
   840  				continue
   841  			}
   842  
   843  			// When updateLinks is called, all valid board links are already compiled and changed to the qualified path beginning with "root"
   844  			if linkIDA[0] != "root" {
   845  				continue
   846  			}
   847  			bida := BoardIDA(f)
   848  			aida := IDA(f)
   849  			if len(bida) != len(aida) {
   850  				prependIDA := aida[:len(aida)-len(bida)]
   851  				fullIDA := []string{"root"}
   852  				// With nested imports, a value may already have been updated with part of the absolute path
   853  				// E.g.,
   854  				// The import prepends path a b c
   855  				// The existing path is b c d
   856  				// So the new path is
   857  				// a b c
   858  				//   b c d
   859  				// -------
   860  				// a b c d
   861  			OUTER:
   862  				for i := 1; i < len(prependIDA); i += 2 {
   863  					for j := 0; i+j < len(prependIDA); j++ {
   864  						if prependIDA[i+j] != linkIDA[1+j] {
   865  							break
   866  						}
   867  						// Reached the end and all common
   868  						if i+j == len(prependIDA)-1 {
   869  							break OUTER
   870  						}
   871  					}
   872  					fullIDA = append(fullIDA, prependIDA[i])
   873  					fullIDA = append(fullIDA, prependIDA[i+1])
   874  				}
   875  				// Chop off "root"
   876  				fullIDA = append(fullIDA, linkIDA[1:]...)
   877  
   878  				kp := d2ast.MakeKeyPath(fullIDA)
   879  				s := d2format.Format(kp)
   880  				f.Primary_.Value = d2ast.MakeValueBox(d2ast.FlatUnquotedString(s)).ScalarBox().Unbox()
   881  			}
   882  		}
   883  		if f.Map() != nil {
   884  			c.updateLinks(f.Map())
   885  		}
   886  	}
   887  }
   888  
   889  func (c *compiler) compileLink(f *Field, refctx *RefContext) {
   890  	val := refctx.Key.Value.ScalarBox().Unbox().ScalarString()
   891  	link, err := d2parser.ParseKey(val)
   892  	if err != nil {
   893  		return
   894  	}
   895  
   896  	scopeIDA := IDA(refctx.ScopeMap)
   897  
   898  	if len(scopeIDA) == 0 {
   899  		return
   900  	}
   901  
   902  	linkIDA := link.IDA()
   903  	if len(linkIDA) == 0 {
   904  		return
   905  	}
   906  
   907  	if linkIDA[0] == "root" {
   908  		c.errorf(refctx.Key.Key, "cannot refer to root in link")
   909  		return
   910  	}
   911  
   912  	// If it doesn't start with one of these reserved words, the link is definitely not a board link.
   913  	if !strings.EqualFold(linkIDA[0], "layers") && !strings.EqualFold(linkIDA[0], "scenarios") && !strings.EqualFold(linkIDA[0], "steps") && linkIDA[0] != "_" {
   914  		return
   915  	}
   916  
   917  	// Chop off the non-board portion of the scope, like if this is being defined on a nested object (e.g. `x.y.z`)
   918  	for i := len(scopeIDA) - 1; i > 0; i-- {
   919  		if strings.EqualFold(scopeIDA[i-1], "layers") || strings.EqualFold(scopeIDA[i-1], "scenarios") || strings.EqualFold(scopeIDA[i-1], "steps") {
   920  			scopeIDA = scopeIDA[:i+1]
   921  			break
   922  		}
   923  		if scopeIDA[i-1] == "root" {
   924  			scopeIDA = scopeIDA[:i]
   925  			break
   926  		}
   927  	}
   928  
   929  	// Resolve underscores
   930  	for len(linkIDA) > 0 && linkIDA[0] == "_" {
   931  		if len(scopeIDA) < 2 {
   932  			// IR compiler only validates bad underscore usage
   933  			// The compiler will validate if the target board actually exists
   934  			c.errorf(refctx.Key.Key, "invalid underscore usage")
   935  			return
   936  		}
   937  		// pop 2 off path per one underscore
   938  		scopeIDA = scopeIDA[:len(scopeIDA)-2]
   939  		linkIDA = linkIDA[1:]
   940  	}
   941  	if len(scopeIDA) == 0 {
   942  		scopeIDA = []string{"root"}
   943  	}
   944  
   945  	// Create the absolute path by appending scope path with value specified
   946  	scopeIDA = append(scopeIDA, linkIDA...)
   947  	kp := d2ast.MakeKeyPath(scopeIDA)
   948  	f.Primary_.Value = d2ast.FlatUnquotedString(d2format.Format(kp))
   949  }
   950  
   951  func (c *compiler) compileEdges(refctx *RefContext) {
   952  	if refctx.Key.Key == nil {
   953  		c._compileEdges(refctx)
   954  		return
   955  	}
   956  
   957  	fa, err := refctx.ScopeMap.EnsureField(refctx.Key.Key, refctx, true, c)
   958  	if err != nil {
   959  		c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
   960  		return
   961  	}
   962  	for _, f := range fa {
   963  		if _, ok := f.Composite.(*Array); ok {
   964  			c.errorf(refctx.Key.Key, "cannot index into array")
   965  			return
   966  		}
   967  		if f.Map() == nil {
   968  			f.Composite = &Map{
   969  				parent: f,
   970  			}
   971  		}
   972  		refctx2 := *refctx
   973  		refctx2.ScopeMap = f.Map()
   974  		c._compileEdges(&refctx2)
   975  	}
   976  }
   977  
   978  func (c *compiler) _compileEdges(refctx *RefContext) {
   979  	eida := NewEdgeIDs(refctx.Key)
   980  	for i, eid := range eida {
   981  		if refctx.Key != nil && refctx.Key.Value.Null != nil {
   982  			refctx.ScopeMap.DeleteEdge(eid)
   983  			continue
   984  		}
   985  
   986  		refctx = refctx.Copy()
   987  		refctx.Edge = refctx.Key.Edges[i]
   988  
   989  		var ea []*Edge
   990  		if eid.Index != nil || eid.Glob {
   991  			ea = refctx.ScopeMap.GetEdges(eid, refctx, c)
   992  			if len(ea) == 0 {
   993  				if !eid.Glob {
   994  					c.errorf(refctx.Edge, "indexed edge does not exist")
   995  				}
   996  				continue
   997  			}
   998  			for _, e := range ea {
   999  				e.References = append(e.References, &EdgeReference{
  1000  					Context_:       refctx,
  1001  					DueToGlob_:     len(c.globRefContextStack) > 0,
  1002  					DueToLazyGlob_: c.lazyGlobBeingApplied,
  1003  				})
  1004  				refctx.ScopeMap.appendFieldReferences(0, refctx.Edge.Src, refctx, c)
  1005  				refctx.ScopeMap.appendFieldReferences(0, refctx.Edge.Dst, refctx, c)
  1006  			}
  1007  		} else {
  1008  			var err error
  1009  			ea, err = refctx.ScopeMap.CreateEdge(eid, refctx, c)
  1010  			if err != nil {
  1011  				c.err.Errors = append(c.err.Errors, err.(d2ast.Error))
  1012  				continue
  1013  			}
  1014  		}
  1015  
  1016  		for _, e := range ea {
  1017  			if refctx.Key.EdgeKey != nil {
  1018  				if e.Map_ == nil {
  1019  					e.Map_ = &Map{
  1020  						parent: e,
  1021  					}
  1022  				}
  1023  				c.compileField(e.Map_, refctx.Key.EdgeKey, refctx)
  1024  			} else {
  1025  				if refctx.Key.Primary.Unbox() != nil {
  1026  					if c.ignoreLazyGlob(e) {
  1027  						return
  1028  					}
  1029  					e.Primary_ = &Scalar{
  1030  						parent: e,
  1031  						Value:  refctx.Key.Primary.Unbox(),
  1032  					}
  1033  				}
  1034  				if refctx.Key.Value.Array != nil {
  1035  					c.errorf(refctx.Key.Value.Unbox(), "edges cannot be assigned arrays")
  1036  					continue
  1037  				} else if refctx.Key.Value.Map != nil {
  1038  					if e.Map_ == nil {
  1039  						e.Map_ = &Map{
  1040  							parent: e,
  1041  						}
  1042  					}
  1043  					c.mapRefContextStack = append(c.mapRefContextStack, refctx)
  1044  					c.compileMap(e.Map_, refctx.Key.Value.Map, refctx.ScopeAST)
  1045  					c.mapRefContextStack = c.mapRefContextStack[:len(c.mapRefContextStack)-1]
  1046  				} else if refctx.Key.Value.ScalarBox().Unbox() != nil {
  1047  					if c.ignoreLazyGlob(e) {
  1048  						return
  1049  					}
  1050  					e.Primary_ = &Scalar{
  1051  						parent: e,
  1052  						Value:  refctx.Key.Value.ScalarBox().Unbox(),
  1053  					}
  1054  				}
  1055  			}
  1056  		}
  1057  	}
  1058  }
  1059  
  1060  func (c *compiler) compileArray(dst *Array, a *d2ast.Array, scopeAST *d2ast.Map) {
  1061  	for _, an := range a.Nodes {
  1062  		var irv Value
  1063  		switch v := an.Unbox().(type) {
  1064  		case *d2ast.Array:
  1065  			ira := &Array{
  1066  				parent: dst,
  1067  			}
  1068  			c.compileArray(ira, v, scopeAST)
  1069  			irv = ira
  1070  		case *d2ast.Map:
  1071  			irm := &Map{
  1072  				parent: dst,
  1073  			}
  1074  			c.compileMap(irm, v, scopeAST)
  1075  			irv = irm
  1076  		case d2ast.Scalar:
  1077  			irv = &Scalar{
  1078  				parent: dst,
  1079  				Value:  v,
  1080  			}
  1081  		case *d2ast.Import:
  1082  			n, ok := c._import(v)
  1083  			if !ok {
  1084  				continue
  1085  			}
  1086  			switch n := n.(type) {
  1087  			case *Field:
  1088  				if v.Spread {
  1089  					a, ok := n.Composite.(*Array)
  1090  					if !ok {
  1091  						c.errorf(v, "can only spread import array into array")
  1092  						continue
  1093  					}
  1094  					dst.Values = append(dst.Values, a.Values...)
  1095  					continue
  1096  				}
  1097  				if n.Composite != nil {
  1098  					irv = n.Composite
  1099  				} else {
  1100  					irv = n.Primary_
  1101  				}
  1102  			case *Map:
  1103  				if v.Spread {
  1104  					c.errorf(v, "can only spread import array into array")
  1105  					continue
  1106  				}
  1107  				irv = n
  1108  			}
  1109  		case *d2ast.Substitution:
  1110  			irv = &Scalar{
  1111  				parent: dst,
  1112  				Value: &d2ast.UnquotedString{
  1113  					Value: []d2ast.InterpolationBox{{Substitution: an.Substitution}},
  1114  				},
  1115  			}
  1116  		}
  1117  
  1118  		dst.Values = append(dst.Values, irv)
  1119  	}
  1120  }
  1121  

View as plain text