2023-01-16 11:52:37 +00:00
package d2ir
import (
2024-08-25 02:02:52 +00:00
"html"
2023-06-04 22:39:00 +00:00
"io/fs"
2024-08-25 02:02:52 +00:00
"net/url"
"path"
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"
2024-11-18 15:01:47 +00:00
"oss.terrastruct.com/d2/lib/textmeasure"
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 )
2025-02-28 18:02:54 +00:00
m . removeSuspendedFields ( )
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 ) {
2024-11-24 18:45:06 +00:00
classes := m . GetField ( d2ast . FlatUnquotedString ( "classes" ) )
2023-02-06 21:32:08 +00:00
if classes == nil || classes . Map ( ) == nil {
return
}
2024-11-24 18:45:06 +00:00
layersField := m . GetField ( d2ast . FlatUnquotedString ( "layers" ) )
2023-02-06 21:32:08 +00:00
if layersField == nil {
return
}
layers := layersField . Map ( )
if layers == nil {
return
}
for _ , lf := range layers . Fields {
if lf . Map ( ) == nil || lf . Primary ( ) != nil {
continue
}
l := lf . Map ( )
2024-11-24 18:45:06 +00:00
lClasses := l . GetField ( d2ast . FlatUnquotedString ( "classes" ) )
2023-02-06 21:32:08 +00:00
if lClasses == nil {
lClasses = classes . Copy ( l ) . ( * Field )
l . Fields = append ( l . Fields , lClasses )
2025-03-23 13:25:15 +00:00
} else if lClasses . Map ( ) != nil {
2023-02-06 21:32:08 +00:00
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 {
2024-11-24 02:49:27 +00:00
if f . Name == nil {
continue
}
2024-11-24 02:40:36 +00:00
if f . Name . ScalarString ( ) == "vars" && f . Name . IsUnquoted ( ) && f . Map ( ) != nil {
2023-07-12 00:19:34 +00:00
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 {
2024-11-24 02:49:27 +00:00
if f . Name != nil && f . Name . ScalarString ( ) == "vars" && f . Name . IsUnquoted ( ) {
2024-08-06 02:18:34 +00:00
c . compileSubstitutions ( f . Map ( ) , varsStack )
2024-11-24 18:45:06 +00:00
c . validateConfigs ( f . Map ( ) . GetField ( d2ast . FlatUnquotedString ( "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 ) ) ) == "" {
2024-11-24 02:56:18 +00:00
c . errorf ( configs . LastRef ( ) . AST ( ) , ` "%s" can only appear at root vars ` , configs . Name . ScalarString ( ) )
2023-07-14 20:08:26 +00:00
return
}
for _ , f := range configs . Map ( ) . Fields {
var val string
if f . Primary ( ) == nil {
2024-11-24 02:40:36 +00:00
if f . Name . ScalarString ( ) != "theme-overrides" && f . Name . ScalarString ( ) != "dark-theme-overrides" && f . Name . ScalarString ( ) != "data" {
2024-11-24 02:56:18 +00:00
c . errorf ( f . LastRef ( ) . AST ( ) , ` "%s" needs a value ` , f . Name . ScalarString ( ) )
2023-07-14 20:08:26 +00:00
continue
}
} else {
val = f . Primary ( ) . Value . ScalarString ( )
}
2024-11-24 02:40:36 +00:00
switch f . Name . ScalarString ( ) {
2023-07-14 20:08:26 +00:00
case "sketch" , "center" :
_ , err := strconv . ParseBool ( val )
if err != nil {
2024-11-24 02:56:18 +00:00
c . errorf ( f . LastRef ( ) . AST ( ) , ` expected a boolean for "%s", got "%s" ` , f . Name . ScalarString ( ) , val )
2023-07-14 20:08:26 +00:00
continue
}
2024-10-15 18:56:39 +00:00
case "theme-overrides" , "dark-theme-overrides" , "data" :
2023-07-14 20:08:26 +00:00
if f . Map ( ) == nil {
2024-11-24 02:56:18 +00:00
c . errorf ( f . LastRef ( ) . AST ( ) , ` "%s" needs a map ` , f . Name . ScalarString ( ) )
2023-07-14 20:08:26 +00:00
continue
}
case "theme-id" , "dark-theme-id" :
valInt , err := strconv . Atoi ( val )
if err != nil {
2024-11-24 02:56:18 +00:00
c . errorf ( f . LastRef ( ) . AST ( ) , ` expected an integer for "%s", got "%s" ` , f . Name . ScalarString ( ) , val )
2023-07-14 20:08:26 +00:00
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 {
2024-11-24 02:56:18 +00:00
c . errorf ( f . LastRef ( ) . AST ( ) , ` expected an integer for "%s", got "%s" ` , f . Name . ScalarString ( ) , val )
2023-07-14 20:08:26 +00:00
continue
}
case "layout-engine" :
default :
2024-11-24 02:56:18 +00:00
c . errorf ( f . LastRef ( ) . AST ( ) , ` "%s" is not a valid config ` , f . Name . ScalarString ( ) )
2023-07-14 20:08:26 +00:00
}
}
}
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 {
2024-08-06 02:18:34 +00:00
for i , vars := range varsStack {
resolvedField = c . resolveSubstitution ( vars , node , box . Substitution , i == 0 )
2023-07-12 00:33:01 +00:00
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 :
2024-08-17 00:48:06 +00:00
m := ParentMap ( n )
2023-07-13 05:20:55 +00:00
if resolvedField . Map ( ) != nil {
2024-08-17 00:48:06 +00:00
ExpandSubstitution ( m , resolvedField . Map ( ) , n )
2023-07-13 05:20:55 +00:00
}
// Remove the placeholder field
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
}
2025-03-22 03:27:17 +00:00
if removedField && len ( m . globs ) > 0 && ! c . lazyGlobBeingApplied {
origGlobStack := c . globContextStack
c . globContextStack = append ( c . globContextStack , m . globs )
for _ , gctx := range m . globs {
old := c . lazyGlobBeingApplied
c . lazyGlobBeingApplied = true
c . compileKey ( gctx . refctx )
c . lazyGlobBeingApplied = old
}
c . globContextStack = origGlobStack
}
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 {
2024-08-06 02:18:34 +00:00
for i , vars := range varsStack {
resolvedField = c . resolveSubstitution ( vars , node , box . Substitution , i == 0 )
2023-07-12 00:33:01 +00:00
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
}
2024-11-18 15:01:47 +00:00
case * d2ast . BlockString :
variables := make ( map [ string ] string )
for _ , vars := range varsStack {
c . collectVariables ( vars , variables )
}
preprocessedValue := textmeasure . ReplaceSubstitutionsMarkdown ( s . Value , variables )
// Update the block string value
s . Value = preprocessedValue
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
}
2024-11-18 15:01:47 +00:00
func ( c * compiler ) collectVariables ( vars * Map , variables map [ string ] string ) {
if vars == nil {
return
}
for _ , f := range vars . Fields {
if f . Primary ( ) != nil {
2024-11-24 02:40:36 +00:00
variables [ f . Name . ScalarString ( ) ] = f . Primary ( ) . Value . ScalarString ( )
2024-11-18 15:01:47 +00:00
} else if f . Map ( ) != nil {
2025-03-24 19:04:46 +00:00
nestedVars := make ( map [ string ] string )
c . collectVariables ( f . Map ( ) , nestedVars )
for k , v := range nestedVars {
variables [ f . Name . ScalarString ( ) + "." + k ] = v
}
2024-11-18 15:01:47 +00:00
c . collectVariables ( f . Map ( ) , variables )
}
}
}
2024-08-06 02:18:34 +00:00
func ( c * compiler ) resolveSubstitution ( vars * Map , node Node , substitution * d2ast . Substitution , isCurrentScopeVars bool ) * Field {
2023-07-12 06:26:06 +00:00
if vars == nil {
return nil
}
2024-08-06 02:18:34 +00:00
fieldNode , fok := node . ( * Field )
parent := ParentField ( node )
2023-07-12 06:26:06 +00:00
for i , p := range substitution . Path {
2024-11-24 18:45:06 +00:00
f := vars . GetField ( p . Unbox ( ) )
2023-07-12 06:26:06 +00:00
if f == nil {
return nil
2023-07-11 02:24:21 +00:00
}
2024-08-06 02:18:34 +00:00
// Consider this case:
//
// ```
// vars: {
// x: a
// }
// hi: {
// vars: {
// x: ${x}-b
// }
// yo: ${x}
// }
// ```
//
// When resolving hi.vars.x, the vars stack includes itself.
// So this next if clause says, "ignore if we're using the current scope's vars to try to resolve a substitution that requires a var from further in the stack"
2024-11-24 02:49:27 +00:00
if fok && fieldNode . Name != nil && fieldNode . Name . ScalarString ( ) == p . Unbox ( ) . ScalarString ( ) && isCurrentScopeVars && parent . Name . ScalarString ( ) == "vars" && parent . Name . IsUnquoted ( ) {
2024-08-06 02:18:34 +00:00
return nil
}
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
}
2024-07-27 16:04:15 +00:00
func ( g * globContext ) copyApplied ( from * globContext ) {
g . appliedFields = make ( map [ string ] struct { } )
for k , v := range from . appliedFields {
g . appliedFields [ k ] = v
}
g . appliedEdges = make ( map [ string ] struct { } )
for k , v := range from . appliedEdges {
g . appliedEdges [ k ] = v
}
}
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 ,
} )
2024-07-18 19:42:08 +00:00
if n . MapKey . NotAmpersand {
ok = ! ok
}
2023-08-30 07:31:36 +00:00
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
2024-11-04 22:58:06 +00:00
if gctx . refctx . Key . HasMultiGlob ( ) {
2024-11-24 18:45:06 +00:00
ks = d2format . Format ( d2ast . MakeKeyPathString ( IDA ( dst ) ) )
2023-08-30 07:31:36 +00:00
} else {
2024-11-24 18:45:06 +00:00
ks = d2format . Format ( d2ast . MakeKeyPathString ( BoardIDA ( dst ) ) )
2023-08-30 07:31:36 +00:00
}
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 ( ) {
2024-12-13 05:52:18 +00:00
gctx2 := g . copy ( )
gctx2 . refctx . ScopeMap = dst
globs = append ( globs , gctx2 )
2023-07-30 20:52:42 +00:00
}
}
2024-07-27 16:01:21 +00:00
} else if NodeBoardKind ( dst ) == BoardScenario {
for _ , g := range previousGlobs {
2024-12-13 05:52:18 +00:00
gctx2 := g . copy ( )
gctx2 . refctx . ScopeMap = dst
if ! g . refctx . Key . HasMultiGlob ( ) {
// Triple globs already apply independently to each board
gctx2 . copyApplied ( g )
}
globs = append ( globs , gctx2 )
}
for _ , g := range previousGlobs {
g2 := g . copy ( )
g2 . refctx . ScopeMap = dst
2024-07-27 16:01:21 +00:00
// We don't want globs applied in a given scenario to affect future boards
// Copying the applied fields and edges keeps the applications scoped to this board
// Note that this is different from steps, where applications carry over
2024-11-04 22:58:06 +00:00
if ! g . refctx . Key . HasMultiGlob ( ) {
2024-07-27 16:01:21 +00:00
// Triple globs already apply independently to each board
g2 . copyApplied ( g )
}
globs = append ( globs , g2 )
}
} else if NodeBoardKind ( dst ) == BoardStep {
2023-07-30 20:52:42 +00:00
for _ , g := range previousGlobs {
2024-12-13 05:52:18 +00:00
gctx2 := g . copy ( )
gctx2 . refctx . ScopeMap = dst
globs = append ( globs , gctx2 )
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 :
2024-09-19 14:57:33 +00:00
// Spread import
2023-06-04 22:39:00 +00:00
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
}
2024-10-19 05:56:00 +00:00
impn . ( Importable ) . SetImportAST ( n . Import )
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 ( ) )
2024-08-25 02:02:52 +00:00
impDir := n . Import . Dir ( )
c . extendLinks ( dst , ParentField ( dst ) , impDir )
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 ) {
2024-07-18 19:42:08 +00:00
if refctx . Key . Ampersand || refctx . Key . NotAmpersand {
2023-07-30 01:02:09 +00:00
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 {
2024-07-18 19:42:08 +00:00
if ! refctx . Key . Ampersand && ! refctx . Key . NotAmpersand {
2023-07-29 23:32:30 +00:00
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
}
2025-03-01 16:58:52 +00:00
keyPath := refctx . Key . Key
if keyPath == nil || len ( keyPath . Path ) == 0 {
return false
}
firstPart := keyPath . Path [ 0 ] . Unbox ( ) . ScalarString ( )
if ( firstPart == "src" || firstPart == "dst" ) && len ( keyPath . Path ) > 1 {
if len ( c . mapRefContextStack ) == 0 {
return false
}
edge := ParentEdge ( refctx . ScopeMap )
if edge == nil {
return false
}
var nodePath [ ] d2ast . String
if firstPart == "src" {
nodePath = edge . ID . SrcPath
} else {
nodePath = edge . ID . DstPath
}
rootMap := RootMap ( refctx . ScopeMap )
node := rootMap . GetField ( nodePath ... )
if node == nil || node . Map ( ) == nil {
return false
}
propKeyPath := & d2ast . KeyPath {
Path : keyPath . Path [ 1 : ] ,
}
propKey := & d2ast . Key {
Key : propKeyPath ,
Value : refctx . Key . Value ,
}
propRefCtx := & RefContext {
Key : propKey ,
ScopeMap : node . Map ( ) ,
ScopeAST : refctx . ScopeAST ,
}
fa , err := node . Map ( ) . EnsureField ( propKeyPath , propRefCtx , false , c )
if err != nil || len ( fa ) == 0 {
return false
}
for _ , f := range fa {
if c . _ampersandFilter ( f , propRefCtx ) {
return true
}
}
return false
}
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 {
2024-08-06 18:11:04 +00:00
if refctx . Key . Value . ScalarBox ( ) . Unbox ( ) . ScalarString ( ) == "*" {
2023-08-17 21:42:52 +00:00
return false
}
2024-08-06 18:11:04 +00:00
// The field/edge has no value for this filter
// But the filter might still match default, e.g. opacity 1
// So we make a fake field for the default
// NOTE: this does not apply to things that themes control, like stroke and fill
// Nor does it apply to layout things like width and height
switch refctx . Key . Key . Last ( ) . ScalarString ( ) {
case "shape" :
f := & Field {
Primary_ : & Scalar {
Value : d2ast . FlatUnquotedString ( "rectangle" ) ,
} ,
}
return c . _ampersandFilter ( f , refctx )
case "border-radius" , "stroke-dash" :
f := & Field {
Primary_ : & Scalar {
Value : d2ast . FlatUnquotedString ( "0" ) ,
} ,
}
return c . _ampersandFilter ( f , refctx )
case "opacity" :
f := & Field {
Primary_ : & Scalar {
Value : d2ast . FlatUnquotedString ( "1" ) ,
} ,
}
return c . _ampersandFilter ( f , refctx )
case "stroke-width" :
f := & Field {
Primary_ : & Scalar {
Value : d2ast . FlatUnquotedString ( "2" ) ,
} ,
}
return c . _ampersandFilter ( f , refctx )
case "icon" , "tooltip" , "link" :
f := & Field {
Primary_ : & Scalar {
Value : d2ast . FlatUnquotedString ( "" ) ,
} ,
}
return c . _ampersandFilter ( f , refctx )
case "shadow" , "multiple" , "3d" , "animated" , "filled" :
f := & Field {
Primary_ : & Scalar {
Value : d2ast . FlatUnquotedString ( "false" ) ,
} ,
}
return c . _ampersandFilter ( f , refctx )
2025-01-21 02:47:26 +00:00
case "leaf" :
raw := refctx . Key . Value . ScalarBox ( ) . Unbox ( ) . ScalarString ( )
boolVal , err := strconv . ParseBool ( raw )
if err != nil {
c . errorf ( refctx . Key , ` &leaf must be "true" or "false", got %q ` , raw )
return false
}
f := refctx . ScopeMap . Parent ( ) . ( * Field )
isLeaf := f . Map ( ) == nil || ! f . Map ( ) . IsContainer ( )
return isLeaf == boolVal
2025-01-21 02:55:23 +00:00
case "connected" :
raw := refctx . Key . Value . ScalarBox ( ) . Unbox ( ) . ScalarString ( )
boolVal , err := strconv . ParseBool ( raw )
if err != nil {
c . errorf ( refctx . Key , ` &connected must be "true" or "false", got %q ` , raw )
return false
}
f := refctx . ScopeMap . Parent ( ) . ( * Field )
isConnected := false
for _ , r := range f . References {
if r . InEdge ( ) {
isConnected = true
break
}
}
return isConnected == boolVal
2024-08-06 18:11:04 +00:00
case "label" :
f := & Field { }
2023-08-17 21:53:51 +00:00
n := refctx . ScopeMap . Parent ( )
2024-08-06 18:11:04 +00:00
if n . Primary ( ) == nil {
switch n := n . ( type ) {
case * Field :
// The label value for fields is their key value
f . Primary_ = & Scalar {
2024-11-24 02:40:36 +00:00
Value : n . Name ,
2023-08-17 21:53:51 +00:00
}
2024-08-06 18:11:04 +00:00
case * Edge :
// But for edges, it's nothing
2023-08-17 21:53:51 +00:00
return false
}
2024-08-06 18:11:04 +00:00
} else {
f . Primary_ = n . Primary ( )
2023-08-17 21:42:52 +00:00
}
2024-08-06 18:11:04 +00:00
return c . _ampersandFilter ( f , refctx )
2025-03-01 16:58:52 +00:00
case "src" :
if len ( c . mapRefContextStack ) == 0 {
return false
}
edge := ParentEdge ( refctx . ScopeMap )
if edge == nil {
return false
}
filterValue := refctx . Key . Value . ScalarBox ( ) . Unbox ( ) . ScalarString ( )
var srcParts [ ] string
for _ , part := range edge . ID . SrcPath {
srcParts = append ( srcParts , part . ScalarString ( ) )
}
2025-03-12 22:05:08 +00:00
container := ParentField ( edge )
if container != nil && container . Name . ScalarString ( ) != "root" {
containerPath := [ ] string { }
curr := container
for curr != nil && curr . Name . ScalarString ( ) != "root" {
containerPath = append ( [ ] string { curr . Name . ScalarString ( ) } , containerPath ... )
curr = ParentField ( curr )
}
srcStart := srcParts [ 0 ]
if ! strings . EqualFold ( srcStart , containerPath [ 0 ] ) {
srcParts = append ( containerPath , srcParts ... )
}
}
2025-03-01 16:58:52 +00:00
srcPath := strings . Join ( srcParts , "." )
return srcPath == filterValue
case "dst" :
if len ( c . mapRefContextStack ) == 0 {
return false
}
edge := ParentEdge ( refctx . ScopeMap )
if edge == nil {
return false
}
filterValue := refctx . Key . Value . ScalarBox ( ) . Unbox ( ) . ScalarString ( )
var dstParts [ ] string
for _ , part := range edge . ID . DstPath {
dstParts = append ( dstParts , part . ScalarString ( ) )
}
2025-03-12 22:05:08 +00:00
// Find the container that holds this edge
// Build the absolute path by prepending the container's path
container := ParentField ( edge )
if container != nil && container . Name . ScalarString ( ) != "root" {
containerPath := [ ] string { }
curr := container
for curr != nil && curr . Name . ScalarString ( ) != "root" {
containerPath = append ( [ ] string { curr . Name . ScalarString ( ) } , containerPath ... )
curr = ParentField ( curr )
}
dstStart := dstParts [ 0 ]
if ! strings . EqualFold ( dstStart , containerPath [ 0 ] ) {
dstParts = append ( containerPath , dstParts ... )
}
}
2025-03-01 16:58:52 +00:00
dstPath := strings . Join ( dstParts , "." )
return dstPath == filterValue
2024-08-06 18:11:04 +00:00
default :
return false
2023-08-17 21:42:52 +00:00
}
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
}
2024-07-18 22:05:49 +00:00
us , ok := refctx . Key . Value . ScalarBox ( ) . Unbox ( ) . ( * d2ast . UnquotedString )
if ok && us . Pattern != nil {
return matchPattern ( f . Primary_ . Value . ScalarString ( ) , us . Pattern )
} else {
if refctx . Key . Value . ScalarBox ( ) . Unbox ( ) . ScalarString ( ) != f . Primary_ . Value . ScalarString ( ) {
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 ) ) {
2024-11-24 02:40:36 +00:00
ParentMap ( f ) . DeleteField ( f . Name . ScalarString ( ) )
2023-07-14 21:22:02 +00:00
return
}
}
2025-02-28 18:02:54 +00:00
if len ( refctx . Key . Edges ) == 0 && ( refctx . Key . Primary . Suspension != nil || refctx . Key . Value . Suspension != nil ) {
2025-02-28 18:17:18 +00:00
if ! c . lazyGlobBeingApplied {
if refctx . Key . Primary . Suspension != nil {
f . suspended = refctx . Key . Primary . Suspension . Value
} else {
f . suspended = refctx . Key . Value . Suspension . Value
}
2025-02-28 18:02:54 +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 {
2024-09-19 14:57:33 +00:00
// Non-spread import
2023-06-04 22:39:00 +00:00
n , ok := c . _import ( refctx . Key . Value . Import )
if ! ok {
return
}
2024-10-19 15:21:32 +00:00
n . ( Importable ) . SetImportAST ( refctx . Key . Value . Import )
2023-06-04 22:39:00 +00:00
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 )
2024-08-25 02:02:52 +00:00
impDir := refctx . Key . Value . Import . Dir ( )
c . extendLinks ( f . Map ( ) , f , impDir )
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.
2024-11-24 02:40:36 +00:00
if f . Name . ScalarString ( ) == "link" && f . Name . IsUnquoted ( ) {
2023-08-13 17:15:13 +00:00
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
}
2024-08-25 02:02:52 +00:00
// When importing a file, all of its board and icon links need to be extended to reflect their new path
func ( c * compiler ) extendLinks ( m * Map , importF * Field , importDir string ) {
2024-07-19 21:03:22 +00:00
nodeBoardKind := NodeBoardKind ( m )
2024-08-25 02:02:52 +00:00
importIDA := IDA ( importF )
2024-07-18 03:05:53 +00:00
for _ , f := range m . Fields {
2025-03-15 03:17:41 +00:00
// A substitute or such
if f . Name == nil {
continue
}
2024-11-24 02:40:36 +00:00
if f . Name . ScalarString ( ) == "link" && f . Name . IsUnquoted ( ) {
2024-07-19 21:03:22 +00:00
if nodeBoardKind != "" {
c . errorf ( f . LastRef ( ) . AST ( ) , "a board itself cannot be linked; only objects within a board can be linked" )
continue
}
2024-07-18 03:05:53 +00:00
val := f . Primary ( ) . Value . ScalarString ( )
2024-09-17 19:27:01 +00:00
u , err := url . Parse ( html . UnescapeString ( val ) )
2025-03-20 15:53:24 +00:00
isRemote := err == nil && ( u . Scheme != "" || strings . HasPrefix ( u . Path , "/" ) )
2024-09-17 19:27:01 +00:00
if isRemote {
continue
}
2024-07-18 03:05:53 +00:00
link , err := d2parser . ParseKey ( val )
if err != nil {
continue
}
linkIDA := link . IDA ( )
if len ( linkIDA ) == 0 {
continue
}
2024-08-26 19:43:56 +00:00
for _ , id := range linkIDA [ 1 : ] {
2024-11-24 18:45:06 +00:00
if id . ScalarString ( ) == "_" && id . IsUnquoted ( ) {
2024-08-27 03:27:56 +00:00
if len ( linkIDA ) < 2 || len ( importIDA ) < 2 {
2024-09-06 20:58:29 +00:00
break
2024-08-27 03:27:56 +00:00
}
2024-11-24 18:45:06 +00:00
linkIDA = append ( [ ] d2ast . String { linkIDA [ 0 ] } , linkIDA [ 2 : ] ... )
2024-08-26 19:43:56 +00:00
importIDA = importIDA [ : len ( importIDA ) - 2 ]
} else {
break
}
}
2024-07-18 03:05:53 +00:00
extendedIDA := append ( importIDA , linkIDA [ 1 : ] ... )
2024-11-24 18:45:06 +00:00
kp := d2ast . MakeKeyPathString ( extendedIDA )
2024-07-18 03:05:53 +00:00
s := d2format . Format ( kp )
f . Primary_ . Value = d2ast . MakeValueBox ( d2ast . FlatUnquotedString ( s ) ) . ScalarBox ( ) . Unbox ( )
}
2024-11-24 02:40:36 +00:00
if f . Name . ScalarString ( ) == "icon" && f . Name . IsUnquoted ( ) && f . Primary ( ) != nil {
2024-08-25 02:02:52 +00:00
val := f . Primary ( ) . Value . ScalarString ( )
2024-11-11 01:55:20 +00:00
// It's likely a substitution
if val == "" {
continue
}
2024-08-25 02:02:52 +00:00
u , err := url . Parse ( html . UnescapeString ( val ) )
2025-03-20 15:53:24 +00:00
isRemoteImg := err == nil && ( u . Scheme != "" || strings . HasPrefix ( u . Path , "/" ) )
2024-08-25 02:02:52 +00:00
if isRemoteImg {
continue
}
val = path . Join ( importDir , val )
f . Primary_ . Value = d2ast . MakeValueBox ( d2ast . FlatUnquotedString ( val ) ) . ScalarBox ( ) . Unbox ( )
}
2024-07-18 03:05:53 +00:00
if f . Map ( ) != nil {
2024-08-25 02:02:52 +00:00
c . extendLinks ( f . Map ( ) , importF , importDir )
2024-07-18 03:05:53 +00:00
}
}
}
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
}
2024-11-24 18:45:06 +00:00
if ! linkIDA [ 0 ] . IsUnquoted ( ) {
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.
2024-11-24 18:45:06 +00:00
if ! strings . EqualFold ( linkIDA [ 0 ] . ScalarString ( ) , "layers" ) && ! strings . EqualFold ( linkIDA [ 0 ] . ScalarString ( ) , "scenarios" ) && ! strings . EqualFold ( linkIDA [ 0 ] . ScalarString ( ) , "steps" ) && linkIDA [ 0 ] . ScalarString ( ) != "_" {
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 -- {
2024-11-24 18:45:06 +00:00
if scopeIDA [ i - 1 ] . IsUnquoted ( ) && ( strings . EqualFold ( scopeIDA [ i - 1 ] . ScalarString ( ) , "layers" ) || strings . EqualFold ( scopeIDA [ i - 1 ] . ScalarString ( ) , "scenarios" ) || strings . EqualFold ( scopeIDA [ i - 1 ] . ScalarString ( ) , "steps" ) ) {
2023-03-01 23:38:02 +00:00
scopeIDA = scopeIDA [ : i + 1 ]
break
}
2024-11-24 18:45:06 +00:00
if scopeIDA [ i - 1 ] . ScalarString ( ) == "root" && scopeIDA [ i - 1 ] . IsUnquoted ( ) {
2023-03-01 23:38:02 +00:00
scopeIDA = scopeIDA [ : i ]
break
}
}
// Resolve underscores
2024-11-24 18:45:06 +00:00
for len ( linkIDA ) > 0 && linkIDA [ 0 ] . ScalarString ( ) == "_" && linkIDA [ 0 ] . IsUnquoted ( ) {
2023-03-01 23:38:02 +00:00
if len ( scopeIDA ) < 2 {
2024-08-26 19:43:56 +00:00
// Leave the underscore. It will fail in compiler as a standalone board,
// but if imported, will get further resolved in extendLinks
break
2023-03-01 23:38:02 +00:00
}
// pop 2 off path per one underscore
scopeIDA = scopeIDA [ : len ( scopeIDA ) - 2 ]
linkIDA = linkIDA [ 1 : ]
}
if len ( scopeIDA ) == 0 {
2024-11-24 18:45:06 +00:00
scopeIDA = [ ] d2ast . String { d2ast . FlatUnquotedString ( "root" ) }
2023-03-01 23:38:02 +00:00
}
// Create the absolute path by appending scope path with value specified
scopeIDA = append ( scopeIDA , linkIDA ... )
2024-11-24 18:45:06 +00:00
kp := d2ast . MakeKeyPathString ( 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 {
2024-06-03 19:27:33 +00:00
if refctx . Key . Primary . Null != nil || refctx . Key . Value . Null != nil {
refctx . ScopeMap . DeleteEdge ( e . ID )
continue
}
2025-02-28 18:02:54 +00:00
2025-03-12 21:29:48 +00:00
if refctx . Key . Value . Map != nil && refctx . Key . Value . Map . HasFilter ( ) {
if e . Map_ == nil {
e . Map_ = & Map {
parent : e ,
}
}
c . mapRefContextStack = append ( c . mapRefContextStack , refctx )
ok := c . ampersandFilterMap ( e . Map_ , refctx . Key . Value . Map , refctx . ScopeAST )
c . mapRefContextStack = c . mapRefContextStack [ : len ( c . mapRefContextStack ) - 1 ]
if ! ok {
continue
}
}
2025-02-28 18:02:54 +00:00
if refctx . Key . Primary . Suspension != nil || refctx . Key . Value . Suspension != nil {
2025-02-28 18:17:18 +00:00
if ! c . lazyGlobBeingApplied {
2025-03-12 22:22:20 +00:00
// Check if edge passes filter before applying suspension
if refctx . Key . Value . Map != nil && refctx . Key . Value . Map . HasFilter ( ) {
if e . Map_ == nil {
e . Map_ = & Map {
parent : e ,
}
}
c . mapRefContextStack = append ( c . mapRefContextStack , refctx )
ok := c . ampersandFilterMap ( e . Map_ , refctx . Key . Value . Map , refctx . ScopeAST )
c . mapRefContextStack = c . mapRefContextStack [ : len ( c . mapRefContextStack ) - 1 ]
if ! ok {
continue
}
}
2025-03-12 19:59:15 +00:00
var suspensionValue bool
2025-02-28 18:17:18 +00:00
if refctx . Key . Primary . Suspension != nil {
2025-03-12 19:59:15 +00:00
suspensionValue = refctx . Key . Primary . Suspension . Value
2025-02-28 18:17:18 +00:00
} else {
2025-03-12 19:59:15 +00:00
suspensionValue = refctx . Key . Value . Suspension . Value
}
e . suspended = suspensionValue
// If we're unsuspending an edge, we should also unsuspend its src and dst objects
2025-03-12 22:05:08 +00:00
// And their ancestors
2025-03-12 19:59:15 +00:00
if ! suspensionValue {
srcPath , dstPath := e . ID . SrcPath , e . ID . DstPath
2025-03-12 22:22:20 +00:00
// Make paths absolute if they're relative
2025-03-12 22:05:08 +00:00
container := ParentField ( e )
if container != nil && container . Name . ScalarString ( ) != "root" {
containerPath := [ ] d2ast . String { }
curr := container
for curr != nil && curr . Name . ScalarString ( ) != "root" {
containerPath = append ( [ ] d2ast . String { curr . Name } , containerPath ... )
curr = ParentField ( curr )
}
if len ( srcPath ) > 0 && ! strings . EqualFold ( srcPath [ 0 ] . ScalarString ( ) , containerPath [ 0 ] . ScalarString ( ) ) {
absSrcPath := append ( [ ] d2ast . String { } , containerPath ... )
srcPath = append ( absSrcPath , srcPath ... )
}
if len ( dstPath ) > 0 && ! strings . EqualFold ( dstPath [ 0 ] . ScalarString ( ) , containerPath [ 0 ] . ScalarString ( ) ) {
absDstPath := append ( [ ] d2ast . String { } , containerPath ... )
dstPath = append ( absDstPath , dstPath ... )
}
}
rootMap := RootMap ( refctx . ScopeMap )
srcObj := rootMap . GetField ( srcPath ... )
dstObj := rootMap . GetField ( dstPath ... )
2025-03-12 19:59:15 +00:00
2025-03-12 22:22:20 +00:00
// Unsuspend source node and all its ancestors
2025-03-12 19:59:15 +00:00
if srcObj != nil {
srcObj . suspended = false
2025-03-12 22:05:08 +00:00
parent := ParentField ( srcObj )
for parent != nil && parent . Name . ScalarString ( ) != "root" {
parent . suspended = false
parent = ParentField ( parent )
}
2025-03-12 19:59:15 +00:00
}
2025-03-12 22:22:20 +00:00
// Unsuspend destination node and all its ancestors
2025-03-12 19:59:15 +00:00
if dstObj != nil {
dstObj . suspended = false
2025-03-12 22:22:20 +00:00
parent := ParentField ( dstObj )
2025-03-12 22:05:08 +00:00
for parent != nil && parent . Name . ScalarString ( ) != "root" {
parent . suspended = false
parent = ParentField ( parent )
}
2025-03-12 19:59:15 +00:00
}
2025-02-28 18:17:18 +00:00
}
2025-02-28 18:02:54 +00:00
}
}
2023-06-24 22:31:58 +00:00
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 )
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 {
2025-03-12 19:59:15 +00:00
if refctx . Key . Primary . Unbox ( ) != nil && refctx . Key . Primary . Suspension == 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 ]
2025-03-12 19:59:15 +00:00
} else if refctx . Key . Value . ScalarBox ( ) . Unbox ( ) != nil && refctx . Key . Value . Suspension == 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
}
2024-10-19 05:56:00 +00:00
n . ( Importable ) . SetImportAST ( v )
2023-06-04 22:39:00 +00:00
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 } } ,
} ,
}
2025-02-25 18:20:39 +00:00
case * d2ast . Comment :
continue
2023-01-16 12:48:45 +00:00
}
dst . Values = append ( dst . Values , irv )
}
2023-01-16 11:52:37 +00:00
}
2025-02-28 18:02:54 +00:00
func ( m * Map ) removeSuspendedFields ( ) {
if m == nil {
return
}
for _ , f := range m . Fields {
if f . Map ( ) != nil {
f . Map ( ) . removeSuspendedFields ( )
}
}
for i := len ( m . Fields ) - 1 ; i >= 0 ; i -- {
2025-02-28 18:17:18 +00:00
if m . Fields [ i ] . Name == nil {
continue
}
2025-02-28 18:02:54 +00:00
_ , isReserved := d2ast . ReservedKeywords [ m . Fields [ i ] . Name . ScalarString ( ) ]
if isReserved {
continue
}
if m . Fields [ i ] . suspended {
m . DeleteField ( m . Fields [ i ] . Name . ScalarString ( ) )
}
}
for _ , e := range m . Edges {
if e . Map ( ) != nil {
e . Map ( ) . removeSuspendedFields ( )
}
}
for i := len ( m . Edges ) - 1 ; i >= 0 ; i -- {
if m . Edges [ i ] . suspended {
m . DeleteEdge ( m . Edges [ i ] . ID )
}
}
}