package d2compiler

import (
	
	
	
	
	
	
	
	
	

	

	
	
	
	
	
	
	
	
)

type CompileOptions struct {
	UTF16Pos bool
	// FS is the file system used for resolving imports in the d2 text.
	// It should correspond to the root path.
	FS fs.FS
}

func ( string,  io.Reader,  *CompileOptions) (*d2graph.Graph, *d2target.Config, error) {
	if  == nil {
		 = &CompileOptions{}
	}

	,  := d2parser.Parse(, , &d2parser.ParseOptions{
		UTF16Pos: .UTF16Pos,
	})
	if  != nil {
		return nil, nil, 
	}

	, ,  := d2ir.Compile(, &d2ir.CompileOptions{
		UTF16Pos: .UTF16Pos,
		FS:       .FS,
	})
	if  != nil {
		return nil, nil, 
	}

	,  := compileIR(, )
	if  != nil {
		return nil, nil, 
	}
	.FS = .FS
	.SortObjectsByAST()
	.SortEdgesByAST()
	,  := compileConfig()
	if  != nil {
		return nil, nil, 
	}
	return , , nil
}

func compileIR( *d2ast.Map,  *d2ir.Map) (*d2graph.Graph, error) {
	 := &compiler{
		err: &d2parser.ParseError{},
	}

	 := d2graph.NewGraph()
	.AST = 
	.BaseAST = 
	.compileBoard(, )
	if len(.err.Errors) > 0 {
		return nil, .err
	}
	.validateBoardLinks()
	if len(.err.Errors) > 0 {
		return nil, .err
	}
	return , nil
}

func ( *compiler) ( *d2graph.Graph,  *d2ir.Map) *d2graph.Graph {
	 = .Copy(nil).(*d2ir.Map)
	// c.preprocessSeqDiagrams(ir)
	.compileMap(.Root, )
	if len(.err.Errors) == 0 {
		.validateKeys(.Root, )
	}
	.validateLabels()
	.validateNear()
	.validateEdges()
	.validatePositionsCompatibility()

	.compileBoardsField(, , "layers")
	.compileBoardsField(, , "scenarios")
	.compileBoardsField(, , "steps")
	if d2ir.ParentMap().CopyBase(nil).Equal(.CopyBase(nil)) {
		if len(.Layers) > 0 || len(.Scenarios) > 0 || len(.Steps) > 0 {
			.IsFolderOnly = true
		}
	}
	if len(.Objects) == 0 {
		.IsFolderOnly = true
	}
	return 
}

func ( *compiler) ( *d2graph.Graph,  *d2ir.Map,  string) {
	 := .GetField(d2ast.FlatUnquotedString())
	if .Map() == nil {
		return
	}
	for ,  := range .Map().Fields {
		 := .Map()
		if .Map() == nil {
			 = &d2ir.Map{}
		}
		if .GetBoard(.Name.ScalarString()) != nil {
			.errorf(.References[0].AST(), "board name %v already used by another board", .Name.ScalarString())
			continue
		}
		 := d2graph.NewGraph()
		.Parent = 
		.AST = .AST().(*d2ast.Map)
		if .BaseAST != nil {
			.BaseAST = findFieldAST(.BaseAST, )
		}
		.compileBoard(, )
		if .Primary() != nil {
			.compileLabel(&.Root.Attributes, )
		}
		.Name = .Name.ScalarString()
		switch  {
		case "layers":
			.Layers = append(.Layers, )
		case "scenarios":
			.Scenarios = append(.Scenarios, )
		case "steps":
			.Steps = append(.Steps, )
		}
	}
}

func findFieldAST( *d2ast.Map,  *d2ir.Field) *d2ast.Map {
	 := []string{}
	 := 
	for {
		 = append([]string{.Name.ScalarString()}, ...)
		 := d2ir.NodeBoardKind()
		if  == "" {
			break
		}
		 = d2ir.ParentField()
	}

	 := 
	for len() > 0 {
		 := [0]
		 := false
		for ,  := range .Nodes {
			if .MapKey == nil {
				continue
			}
			if .MapKey.Key == nil {
				continue
			}
			if len(.MapKey.Key.Path) != 1 {
				continue
			}
			 := .MapKey.Key.Path[0].Unbox().ScalarString()
			if  ==  {
				 = .MapKey.Value.Map
				// The BaseAST is only used for making edits to the AST (through d2oracle)
				// If there's no Map for a given board, either it's an empty layer or set to an import
				// Either way, in order to make edits, it needs to be expanded into a Map to add lines to
				if  == nil {
					.MapKey.Value.Map = &d2ast.Map{
						Range: d2ast.MakeRange(",1:0:0-1:0:0"),
					}
					if .MapKey.Value.Import != nil {
						 := &d2ast.Import{
							Range:  d2ast.MakeRange(",1:0:0-1:0:0"),
							Spread: true,
							Pre:    .MapKey.Value.Import.Pre,
							Path:   .MapKey.Value.Import.Path,
						}
						.MapKey.Value.Map.Nodes = append(.MapKey.Value.Map.Nodes, d2ast.MapNodeBox{
							Import: ,
						})

					}
					 = .MapKey.Value.Map
				}
				 = true
				break
			}
		}
		if ! {
			return nil
		}
		 = [1:]
	}

	return 
}

type compiler struct {
	err *d2parser.ParseError
}

func ( *compiler) ( d2ast.Node,  string,  ...interface{}) {
	 := d2parser.Errorf(, , ...).(d2ast.Error)
	if .err.ErrorsLookup == nil {
		.err.ErrorsLookup = make(map[d2ast.Error]struct{})
	}
	if ,  := .err.ErrorsLookup[]; ! {
		.err.Errors = append(.err.Errors, )
		.err.ErrorsLookup[] = struct{}{}
	}
}

func ( *compiler) ( *d2graph.Object,  *d2ir.Map) {
	 := .GetField(d2ast.FlatUnquotedString("class"))
	if  != nil {
		var  []string
		if .Primary() != nil {
			 = append(, .Primary().String())
		} else if .Composite != nil {
			if ,  := .Composite.(*d2ir.Array);  {
				for ,  := range .Values {
					if ,  := .(*d2ir.Scalar);  {
						 = append(, .Value.ScalarString())
					} else {
						.errorf(.LastPrimaryKey(), "invalid value in array")
					}
				}
			}
		}

		for ,  := range  {
			 := .GetClassMap()
			if  != nil {
				.(, )
			} else {
				if strings.Contains(, ",") {
					 := strings.Split(, ",")
					 := true
					for ,  := range  {
						 = strings.TrimSpace()
						if .GetClassMap() == nil {
							 = false
							break
						}
					}
					if  {
						.errorf(.LastRef().AST(), `class "%s" not found. Did you mean to use ";" to separate array items?`, )
					}
				}
			}
		}
	}
	 := .GetField(d2ast.FlatUnquotedString("shape"))
	if  != nil {
		if .Composite != nil {
			.errorf(.LastPrimaryKey(), "reserved field shape does not accept composite")
		} else {
			.compileField(, )
		}
	}
	for ,  := range .Fields {
		if .Name.ScalarString() == "shape" && .Name.IsUnquoted() {
			continue
		}
		if ,  := d2ast.BoardKeywords[.Name.ScalarString()];  && .Name.IsUnquoted() {
			continue
		}
		.compileField(, )
	}

	if !.IsClass() {
		switch .Shape.Value {
		case d2target.ShapeClass:
			.compileClass()
		case d2target.ShapeSQLTable:
			.compileSQLTable()
		}

		for ,  := range .Edges {
			.compileEdge(, )
		}
	}
}

func ( *compiler) ( *d2graph.Object,  *d2ir.Field) {
	 := strings.ToLower(.Name.ScalarString())
	,  := d2ast.StyleKeywords[]
	if  && .Name.IsUnquoted() {
		.errorf(.LastRef().AST(), "%v must be style.%v", .Name.ScalarString(), .Name.ScalarString())
		return
	}
	,  := d2ast.SimpleReservedKeywords[]
	 =  && .Name.IsUnquoted()
	if .Name.ScalarString() == "classes" && .Name.IsUnquoted() {
		if .Map() != nil {
			if len(.Map().Edges) > 0 {
				.errorf(.Map().Edges[0].LastRef().AST(), "classes cannot contain an edge")
			}
			for ,  := range .Map().Fields {
				if .Map() == nil {
					continue
				}
				for ,  := range .Map().Fields {
					if ,  := d2ast.ReservedKeywords[.Name.ScalarString()]; !( && .Name.IsUnquoted()) {
						.errorf(.LastRef().AST(), "%s is an invalid class field, must be reserved keyword", .Name.ScalarString())
					}
					if .Name.ScalarString() == "class" && .Name.IsUnquoted() {
						.errorf(.LastRef().AST(), `"class" cannot appear within "classes"`)
					}
				}
			}
		}
		return
	} else if .Name.ScalarString() == "vars" && .Name.IsUnquoted() {
		return
	} else if (.Name.ScalarString() == "source-arrowhead" || .Name.ScalarString() == "target-arrowhead") && .Name.IsUnquoted() {
		.errorf(.LastRef().AST(), `%#v can only be used on connections`, .Name.ScalarString())
		return

	} else if  {
		.compileReserved(&.Attributes, )
		return
	} else if .Name.ScalarString() == "style" && .Name.IsUnquoted() {
		if .Map() == nil || len(.Map().Fields) == 0 {
			.errorf(.LastRef().AST(), `"style" expected to be set to a map of key-values, or contain an additional keyword like "style.opacity: 0.4"`)
			return
		}
		.compileStyle(&.Attributes, .Map())
		return
	}

	if .Parent != nil {
		if strings.EqualFold(.Parent.Shape.Value, d2target.ShapeSQLTable) {
			.errorf(.LastRef().AST(), "sql_table columns cannot have children")
			return
		}
		if strings.EqualFold(.Parent.Shape.Value, d2target.ShapeClass) {
			.errorf(.LastRef().AST(), "class fields cannot have children")
			return
		}
	}

	 := 
	 = .EnsureChild(([]d2ast.String{.Name}))
	if .Primary() != nil {
		.compileLabel(&.Attributes, )
	}
	if .Map() != nil {
		.compileMap(, .Map())
	}

	if .Label.MapKey == nil {
		.Label.MapKey = .LastPrimaryKey()
	}
	for ,  := range .References {
		if .Primary() {
			if .Context_.Key.Value.Map != nil {
				.Map = .Context_.Key.Value.Map
			}
		}
		 := d2graph.Reference{
			Key:          .KeyPath,
			KeyPathIndex: .KeyPathIndex(),

			MapKey:          .Context_.Key,
			MapKeyEdgeIndex: .Context_.EdgeIndex(),
			Scope:           .Context_.Scope,
			ScopeAST:        .Context_.ScopeAST,
			ScopeObj:        ,
			IsVar:           d2ir.IsVar(.Context_.ScopeMap),
		}
		if .Context_.ScopeMap != nil && !d2ir.IsVar(.Context_.ScopeMap) {
			 := d2ir.BoardIDA(.Context_.ScopeMap)
			.ScopeObj = .Graph.Root.EnsureChild()
		}
		.References = append(.References, )
	}
}

func ( *compiler) ( *d2graph.Attributes,  d2ir.Node) {
	 := .Primary().Value
	switch scalar := .(type) {
	case *d2ast.BlockString:
		if strings.TrimSpace(.ScalarString()) == "" {
			.errorf(.LastPrimaryKey(), "block string cannot be empty")
		}
		.Language = .Tag
		,  := ShortToFullLanguageAliases[.Tag]
		if  {
			.Language = 
		}
		switch .Language {
		case "latex":
			.Shape.Value = d2target.ShapeText
		case "markdown":
			,  := textmeasure.RenderMarkdown(.ScalarString())
			if  != nil {
				.errorf(.LastPrimaryKey(), "malformed Markdown")
			}
			 = "<div>" +  + "</div>"
			var  interface{}
			 = xml.Unmarshal([]byte(), &)
			if  != nil {
				switch xmlErr := .(type) {
				case *xml.SyntaxError:
					.errorf(.LastPrimaryKey(), "malformed Markdown: %s", .Msg)
				default:
					.errorf(.LastPrimaryKey(), "malformed Markdown: %s", .Error())
				}
			}
			.Shape.Value = d2target.ShapeText
		default:
			.Shape.Value = d2target.ShapeCode
		}
		.Label.Value = .ScalarString()
	default:
		.Label.Value = .ScalarString()
	}
	.Label.MapKey = .LastPrimaryKey()
}

func ( *compiler) ( *d2graph.Attributes,  *d2ir.Field) {
	 := .Name
	if .Map() != nil {
		for ,  := range .Map().Fields {
			if .Name.ScalarString() == "near" && .Name.IsUnquoted() {
				if .Primary() == nil {
					.errorf(.LastPrimaryKey(), `invalid "near" field`)
				} else {
					 := .Primary().Value
					switch scalar := .(type) {
					case *d2ast.Null:
						.LabelPosition = nil
					default:
						if ,  := d2ast.LabelPositions[.ScalarString()]; ! {
							.errorf(.LastPrimaryKey(), `invalid "near" field`)
						} else {
							switch .ScalarString() {
							case "label":
								.LabelPosition = &d2graph.Scalar{}
								.LabelPosition.Value = .ScalarString()
								.LabelPosition.MapKey = .LastPrimaryKey()
							case "icon":
								.IconPosition = &d2graph.Scalar{}
								.IconPosition.Value = .ScalarString()
								.IconPosition.MapKey = .LastPrimaryKey()
							}
						}
					}
				}
			} else {
				if .LastPrimaryKey() != nil {
					.errorf(.LastPrimaryKey(), `unexpected field %s`, .Name.ScalarString())
				}
			}
		}
		if len(.Map().Edges) > 0 {
			.errorf(.LastPrimaryKey(), "unexpected edges in map")
		}
	}
}

func ( *compiler) ( *d2graph.Attributes,  *d2ir.Field) {
	if .Primary() == nil {
		if .Composite != nil {
			switch .Name.ScalarString() {
			case "class":
				if ,  := .Composite.(*d2ir.Array);  {
					for ,  := range .Values {
						if ,  := .(*d2ir.Scalar);  {
							.Classes = append(.Classes, .Value.ScalarString())
						}
					}
				}
			case "constraint":
				if ,  := .Composite.(*d2ir.Array);  {
					for ,  := range .Values {
						if ,  := .(*d2ir.Scalar);  {
							switch .Value.(type) {
							case *d2ast.Null:
								.Constraint = append(.Constraint, "null")
							default:
								.Constraint = append(.Constraint, .Value.ScalarString())
							}
						}
					}
				}
			case "label", "icon":
				.compilePosition(, )
			default:
				.errorf(.LastPrimaryKey(), "reserved field %v does not accept composite", .Name.ScalarString())
			}
		} else {
			.errorf(.LastRef().AST(), `reserved field "%v" must have a value`, .Name.ScalarString())
		}
		return
	}
	 := .Primary().Value
	switch .Name.ScalarString() {
	case "label":
		.compileLabel(, )
		.compilePosition(, )
	case "shape":
		 := d2target.IsShape(.ScalarString())
		,  := d2target.Arrowheads[.ScalarString()]
		if ! && ! {
			.errorf(, "unknown shape %q", .ScalarString())
			return
		}
		.Shape.Value = .ScalarString()
		if strings.EqualFold(.Shape.Value, d2target.ShapeCode) {
			// Explicit code shape is plaintext.
			.Language = d2target.ShapeText
		}
		.Shape.MapKey = .LastPrimaryKey()
	case "icon":
		,  := url.Parse(.ScalarString())
		if  != nil {
			.errorf(, "bad icon url %#v: %s", .ScalarString(), )
			return
		}
		.Icon = 
		.compilePosition(, )
	case "near":
		,  := d2parser.ParseKey(.ScalarString())
		if  != nil {
			.errorf(, "bad near key %#v: %s", .ScalarString(), )
			return
		}
		.Range = .GetRange()
		.NearKey = 
	case "tooltip":
		.Tooltip = &d2graph.Scalar{}
		.Tooltip.Value = .ScalarString()
		.Tooltip.MapKey = .LastPrimaryKey()
	case "width":
		,  := strconv.Atoi(.ScalarString())
		if  != nil {
			.errorf(, "non-integer width %#v: %s", .ScalarString(), )
			return
		}
		.WidthAttr = &d2graph.Scalar{}
		.WidthAttr.Value = .ScalarString()
		.WidthAttr.MapKey = .LastPrimaryKey()
	case "height":
		,  := strconv.Atoi(.ScalarString())
		if  != nil {
			.errorf(, "non-integer height %#v: %s", .ScalarString(), )
			return
		}
		.HeightAttr = &d2graph.Scalar{}
		.HeightAttr.Value = .ScalarString()
		.HeightAttr.MapKey = .LastPrimaryKey()
	case "top":
		,  := strconv.Atoi(.ScalarString())
		if  != nil {
			.errorf(, "non-integer top %#v: %s", .ScalarString(), )
			return
		}
		if  < 0 {
			.errorf(, "top must be a non-negative integer: %#v", .ScalarString())
			return
		}
		.Top = &d2graph.Scalar{}
		.Top.Value = .ScalarString()
		.Top.MapKey = .LastPrimaryKey()
	case "left":
		,  := strconv.Atoi(.ScalarString())
		if  != nil {
			.errorf(, "non-integer left %#v: %s", .ScalarString(), )
			return
		}
		if  < 0 {
			.errorf(, "left must be a non-negative integer: %#v", .ScalarString())
			return
		}
		.Left = &d2graph.Scalar{}
		.Left.Value = .ScalarString()
		.Left.MapKey = .LastPrimaryKey()
	case "link":
		.Link = &d2graph.Scalar{}
		.Link.Value = .ScalarString()
		.Link.MapKey = .LastPrimaryKey()
	case "direction":
		 := []string{"up", "down", "right", "left"}
		if !go2.Contains(, .ScalarString()) {
			.errorf(, `direction must be one of %v, got %q`, strings.Join(, ", "), .ScalarString())
			return
		}
		.Direction.Value = .ScalarString()
		.Direction.MapKey = .LastPrimaryKey()
	case "constraint":
		if ,  := .(d2ast.String); ! {
			.errorf(.LastPrimaryKey(), "constraint value must be a string")
			return
		}
		.Constraint = append(.Constraint, .ScalarString())
	case "grid-rows":
		,  := strconv.Atoi(.ScalarString())
		if  != nil {
			.errorf(, "non-integer grid-rows %#v: %s", .ScalarString(), )
			return
		}
		if  <= 0 {
			.errorf(, "grid-rows must be a positive integer: %#v", .ScalarString())
			return
		}
		.GridRows = &d2graph.Scalar{}
		.GridRows.Value = .ScalarString()
		.GridRows.MapKey = .LastPrimaryKey()
	case "grid-columns":
		,  := strconv.Atoi(.ScalarString())
		if  != nil {
			.errorf(, "non-integer grid-columns %#v: %s", .ScalarString(), )
			return
		}
		if  <= 0 {
			.errorf(, "grid-columns must be a positive integer: %#v", .ScalarString())
			return
		}
		.GridColumns = &d2graph.Scalar{}
		.GridColumns.Value = .ScalarString()
		.GridColumns.MapKey = .LastPrimaryKey()
	case "grid-gap":
		,  := strconv.Atoi(.ScalarString())
		if  != nil {
			.errorf(, "non-integer grid-gap %#v: %s", .ScalarString(), )
			return
		}
		if  < 0 {
			.errorf(, "grid-gap must be a non-negative integer: %#v", .ScalarString())
			return
		}
		.GridGap = &d2graph.Scalar{}
		.GridGap.Value = .ScalarString()
		.GridGap.MapKey = .LastPrimaryKey()
	case "vertical-gap":
		,  := strconv.Atoi(.ScalarString())
		if  != nil {
			.errorf(, "non-integer vertical-gap %#v: %s", .ScalarString(), )
			return
		}
		if  < 0 {
			.errorf(, "vertical-gap must be a non-negative integer: %#v", .ScalarString())
			return
		}
		.VerticalGap = &d2graph.Scalar{}
		.VerticalGap.Value = .ScalarString()
		.VerticalGap.MapKey = .LastPrimaryKey()
	case "horizontal-gap":
		,  := strconv.Atoi(.ScalarString())
		if  != nil {
			.errorf(, "non-integer horizontal-gap %#v: %s", .ScalarString(), )
			return
		}
		if  < 0 {
			.errorf(, "horizontal-gap must be a non-negative integer: %#v", .ScalarString())
			return
		}
		.HorizontalGap = &d2graph.Scalar{}
		.HorizontalGap.Value = .ScalarString()
		.HorizontalGap.MapKey = .LastPrimaryKey()
	case "class":
		.Classes = append(.Classes, .ScalarString())
	case "classes":
	}

	if .Link != nil && .Label.Value != "" {
		,  := url.ParseRequestURI(.Label.Value)
		if  == nil && .Host != "" {
			.errorf(, "Label cannot be set to URL when link is also set (for security)")
		}
	}

	if .Link != nil && .Tooltip != nil {
		,  := url.ParseRequestURI(.Tooltip.Value)
		if  == nil && .Host != "" {
			.errorf(, "Tooltip cannot be set to URL when link is also set (for security)")
		}
	}
}

func ( *compiler) ( *d2graph.Attributes,  *d2ir.Map) {
	for ,  := range .Fields {
		.compileStyleField(, )
	}
}

func ( *compiler) ( *d2graph.Attributes,  *d2ir.Field) {
	if ,  := d2ast.StyleKeywords[strings.ToLower(.Name.ScalarString())]; !( && .Name.IsUnquoted()) {
		.errorf(.LastRef().AST(), `invalid style keyword: "%s"`, .Name.ScalarString())
		return
	}
	if .Primary() == nil {
		return
	}
	compileStyleFieldInit(, )
	 := .Primary().Value
	 := .Style.Apply(.Name.ScalarString(), .ScalarString())
	if  != nil {
		.errorf(, .Error())
		return
	}
}

func compileStyleFieldInit( *d2graph.Attributes,  *d2ir.Field) {
	switch .Name.ScalarString() {
	case "opacity":
		.Style.Opacity = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "stroke":
		.Style.Stroke = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "fill":
		.Style.Fill = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "fill-pattern":
		.Style.FillPattern = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "stroke-width":
		.Style.StrokeWidth = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "stroke-dash":
		.Style.StrokeDash = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "border-radius":
		.Style.BorderRadius = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "shadow":
		.Style.Shadow = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "3d":
		.Style.ThreeDee = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "multiple":
		.Style.Multiple = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "font":
		.Style.Font = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "font-size":
		.Style.FontSize = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "font-color":
		.Style.FontColor = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "animated":
		.Style.Animated = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "bold":
		.Style.Bold = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "italic":
		.Style.Italic = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "underline":
		.Style.Underline = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "filled":
		.Style.Filled = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "width":
		.WidthAttr = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "height":
		.HeightAttr = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "top":
		.Top = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "left":
		.Left = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "double-border":
		.Style.DoubleBorder = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	case "text-transform":
		.Style.TextTransform = &d2graph.Scalar{MapKey: .LastPrimaryKey()}
	}
}

func ( *compiler) ( *d2graph.Object,  *d2ir.Edge) {
	,  := .Connect(.ID.SrcPath, .ID.DstPath, .ID.SrcArrow, .ID.DstArrow, "")
	if  != nil {
		.errorf(.References[0].AST(), .Error())
		return
	}

	if .Primary() != nil {
		.compileLabel(&.Attributes, )
	}
	if .Map() != nil {
		.compileEdgeMap(, .Map())
	}

	.Label.MapKey = .LastPrimaryKey()
	for ,  := range .References {
		 := d2graph.EdgeReference{
			Edge:            .Context_.Edge,
			MapKey:          .Context_.Key,
			MapKeyEdgeIndex: .Context_.EdgeIndex(),
			Scope:           .Context_.Scope,
			ScopeAST:        .Context_.ScopeAST,
			ScopeObj:        ,
		}
		if .Context_.ScopeMap != nil && !d2ir.IsVar(.Context_.ScopeMap) {
			 := d2ir.BoardIDA(.Context_.ScopeMap)
			.ScopeObj = .Src.Graph.Root.EnsureChild()
		}
		.References = append(.References, )
	}
}

func ( *compiler) ( *d2graph.Edge,  *d2ir.Map) {
	 := .GetField(d2ast.FlatUnquotedString("class"))
	if  != nil {
		var  []string
		if .Primary() != nil {
			 = append(, .Primary().String())
		} else if .Composite != nil {
			if ,  := .Composite.(*d2ir.Array);  {
				for ,  := range .Values {
					if ,  := .(*d2ir.Scalar);  {
						 = append(, .Value.ScalarString())
					} else {
						.errorf(.LastPrimaryKey(), "invalid value in array")
					}
				}
			}
		}

		for ,  := range  {
			 := .GetClassMap()
			if  != nil {
				.(, )
			}
		}
	}
	for ,  := range .Fields {
		,  := d2ast.ReservedKeywords[.Name.ScalarString()]
		if !( && .Name.IsUnquoted()) {
			.errorf(.References[0].AST(), `edge map keys must be reserved keywords`)
			continue
		}
		.compileEdgeField(, )
	}
}

func ( *compiler) ( *d2graph.Edge,  *d2ir.Field) {
	 := strings.ToLower(.Name.ScalarString())
	,  := d2ast.StyleKeywords[]
	 =  && .Name.IsUnquoted()
	if  {
		.errorf(.LastRef().AST(), "%v must be style.%v", .Name.ScalarString(), .Name.ScalarString())
		return
	}
	,  := d2ast.SimpleReservedKeywords[]
	if  {
		.compileReserved(&.Attributes, )
		return
	} else if .Name.ScalarString() == "style" {
		if .Map() == nil {
			return
		}
		.compileStyle(&.Attributes, .Map())
		return
	}

	if (.Name.ScalarString() == "source-arrowhead" || .Name.ScalarString() == "target-arrowhead") && .Name.IsUnquoted() {
		.compileArrowheads(, )
	}
}

func ( *compiler) ( *d2graph.Edge,  *d2ir.Field) {
	var  *d2graph.Attributes
	if .Name.ScalarString() == "source-arrowhead" {
		if .SrcArrowhead == nil {
			.SrcArrowhead = &d2graph.Attributes{}
		}
		 = .SrcArrowhead
	} else {
		if .DstArrowhead == nil {
			.DstArrowhead = &d2graph.Attributes{}
		}
		 = .DstArrowhead
	}

	if .Primary() != nil {
		.compileLabel(, )
	}

	if .Map() != nil {
		for ,  := range .Map().Fields {
			 := strings.ToLower(.Name.ScalarString())
			,  := d2ast.SimpleReservedKeywords[]
			 =  && .Name.IsUnquoted()
			if  {
				.compileReserved(, )
				continue
			} else if .Name.ScalarString() == "style" && .Name.IsUnquoted() {
				if .Map() == nil {
					continue
				}
				.compileStyle(, .Map())
				continue
			} else {
				.errorf(.LastRef().AST(), `source-arrowhead/target-arrowhead map keys must be reserved keywords`)
				continue
			}
		}
	}
}

// TODO add more, e.g. C, bash
var ShortToFullLanguageAliases = map[string]string{
	"md":  "markdown",
	"tex": "latex",
	"js":  "javascript",
	"go":  "golang",
	"py":  "python",
	"rb":  "ruby",
	"ts":  "typescript",
}
var FullToShortLanguageAliases map[string]string

func ( *compiler) ( *d2graph.Object) {
	.Class = &d2target.Class{}
	for ,  := range .ChildrenArray {
		 := "public"
		 := .IDVal
		// See https://www.uml-diagrams.org/visibility.html
		if  != "" {
			switch [0] {
			case '+':
				 = [1:]
			case '-':
				 = "private"
				 = [1:]
			case '#':
				 = "protected"
				 = [1:]
			}
		}

		if !strings.Contains(.IDVal, "(") {
			 := .Label.Value
			if  == .IDVal {
				 = ""
			}
			.Class.Fields = append(.Class.Fields, d2target.ClassField{
				Name:       ,
				Type:       ,
				Visibility: ,
			})
		} else {
			// TODO: Not great, AST should easily allow specifying alternate primary field
			// as an explicit label should change the name.
			 := .Label.Value
			if  == .IDVal {
				 = "void"
			}
			.Class.Methods = append(.Class.Methods, d2target.ClassMethod{
				Name:       ,
				Return:     ,
				Visibility: ,
			})
		}
	}

	for ,  := range .ChildrenArray {
		for  := 0;  < len(.Graph.Objects); ++ {
			if .Graph.Objects[] ==  {
				.Graph.Objects = append(.Graph.Objects[:], .Graph.Objects[+1:]...)
				--
			}
		}
	}
	.Children = nil
	.ChildrenArray = nil
}

func ( *compiler) ( *d2graph.Object) {
	.SQLTable = &d2target.SQLTable{}
	for ,  := range .ChildrenArray {
		 := .Label.Value
		if  == .IDVal {
			// Not great, AST should easily allow specifying alternate primary field
			// as an explicit label should change the name.
			 = ""
		}
		 := d2target.SQLColumn{
			Name:       d2target.Text{Label: .IDVal},
			Type:       d2target.Text{Label: },
			Constraint: .Constraint,
		}
		.SQLTable.Columns = append(.SQLTable.Columns, )
	}

	for ,  := range .ChildrenArray {
		for  := 0;  < len(.Graph.Objects); ++ {
			if .Graph.Objects[] ==  {
				.Graph.Objects = append(.Graph.Objects[:], .Graph.Objects[+1:]...)
				--
			}
		}
	}
	.Children = nil
	.ChildrenArray = nil
}

func ( *compiler) ( *d2graph.Object,  *d2ir.Map) {
	for ,  := range .Fields {
		if ,  := d2ast.BoardKeywords[.Name.ScalarString()];  && .Name.IsUnquoted() {
			continue
		}
		.validateKey(, )
	}
}

func ( *compiler) ( *d2graph.Object,  *d2ir.Field) {
	 := strings.ToLower(.Name.ScalarString())
	,  := d2ast.ReservedKeywords[]
	 =  && .Name.IsUnquoted()
	if  {
		switch .Shape.Value {
		case d2target.ShapeCircle, d2target.ShapeSquare:
			 := ( == "width" && .HeightAttr != nil) || ( == "height" && .WidthAttr != nil)
			if  && .WidthAttr.Value != .HeightAttr.Value {
				.errorf(.LastPrimaryKey(), "width and height must be equal for %s shapes", .Shape.Value)
			}
		}

		switch .Name.ScalarString() {
		case "style":
			if .Style.ThreeDee != nil {
				if !strings.EqualFold(.Shape.Value, d2target.ShapeSquare) && !strings.EqualFold(.Shape.Value, d2target.ShapeRectangle) && !strings.EqualFold(.Shape.Value, d2target.ShapeHexagon) {
					.errorf(.Style.ThreeDee.MapKey, `key "3d" can only be applied to squares, rectangles, and hexagons`)
				}
			}
			if .Style.DoubleBorder != nil {
				if .Shape.Value != "" && !strings.EqualFold(.Shape.Value, d2target.ShapeSquare) && !strings.EqualFold(.Shape.Value, d2target.ShapeRectangle) && !strings.EqualFold(.Shape.Value, d2target.ShapeCircle) && !strings.EqualFold(.Shape.Value, d2target.ShapeOval) {
					.errorf(.Style.DoubleBorder.MapKey, `key "double-border" can only be applied to squares, rectangles, circles, ovals`)
				}
			}
		case "shape":
			if strings.EqualFold(.Shape.Value, d2target.ShapeImage) && .Icon == nil {
				.errorf(.LastPrimaryKey(), `image shape must include an "icon" field`)
			}

			 := d2target.IsShape(.Shape.Value)
			,  := d2target.Arrowheads[.Shape.Value]
			if ! &&  {
				.errorf(.LastPrimaryKey(), fmt.Sprintf(`invalid shape, can only set "%s" for arrowheads`, .Shape.Value))
			}
		case "constraint":
			if !strings.EqualFold(.Shape.Value, d2target.ShapeSQLTable) {
				.errorf(.LastPrimaryKey(), `"constraint" keyword can only be used in "sql_table" shapes`)
			}
		}
		return
	}

	if strings.EqualFold(.Shape.Value, d2target.ShapeImage) && .OuterSequenceDiagram() == nil {
		.errorf(.LastRef().AST(), "image shapes cannot have children.")
		return
	}

	,  := .HasChild([]string{.Name.ScalarString()})
	if  && .Map() != nil {
		.validateKeys(, .Map())
	}
}

func ( *compiler) ( *d2graph.Graph) {
	for ,  := range .Objects {
		if strings.EqualFold(.Shape.Value, d2target.ShapeText) {
			if .Attributes.Language != "" {
				// blockstrings have already been validated
				continue
			}
			if strings.TrimSpace(.Label.Value) == "" {
				.errorf(.Label.MapKey, "shape text must have a non-empty label")
			}
		} else if strings.EqualFold(.Shape.Value, d2target.ShapeSQLTable) {
			if strings.Contains(.Label.Value, "\n") {
				.errorf(.Label.MapKey, "shape sql_table cannot have newlines in label")
			}
		}
	}
}

func ( *compiler) ( *d2graph.Graph) {
	for ,  := range .Objects {
		if .NearKey != nil {
			,  := .Root.HasChild(d2graph.Key(.NearKey))
			,  := d2ast.NearConstants[d2graph.Key(.NearKey)[0]]
			if  {
				// Doesn't make sense to set near to an ancestor or descendant
				 := false
				for  := ;  != nil;  = .Parent {
					if  ==  {
						 = true
						break
					}
				}
				if  {
					.errorf(.NearKey, "near keys cannot be set to an ancestor")
					continue
				}
				 := false
				for  := ;  != nil;  = .Parent {
					if  ==  {
						 = true
						break
					}
				}
				if  {
					.errorf(.NearKey, "near keys cannot be set to an descendant")
					continue
				}
				if .OuterSequenceDiagram() != nil {
					.errorf(.NearKey, "near keys cannot be set to an object within sequence diagrams")
					continue
				}
				if .NearKey != nil {
					,  := d2ast.NearConstants[d2graph.Key(.NearKey)[0]]
					if  {
						.errorf(.NearKey, "near keys cannot be set to an object with a constant near key")
						continue
					}
				}
				if .ClosestGridDiagram() != nil {
					.errorf(.NearKey, "near keys cannot be set to descendants of special objects, like grid cells")
					continue
				}
				if .OuterSequenceDiagram() != nil {
					.errorf(.NearKey, "near keys cannot be set to descendants of special objects, like sequence diagram actors")
					continue
				}
			} else if  {
				if .Parent != .Root {
					.errorf(.NearKey, "constant near keys can only be set on root level shapes")
					continue
				}
			} else {
				.errorf(.NearKey, "near key %#v must be the absolute path to a shape or one of the following constants: %s", d2format.Format(.NearKey), strings.Join(d2ast.NearConstantsArray, ", "))
				continue
			}
		}
	}

	for ,  := range .Edges {
		if .Src.IsConstantNear() && .Dst.IsDescendantOf(.Src) {
			.errorf(.GetAstEdge(), "edge from constant near %#v cannot enter itself", .Src.AbsID())
			continue
		}
		if .Dst.IsConstantNear() && .Src.IsDescendantOf(.Dst) {
			.errorf(.GetAstEdge(), "edge from constant near %#v cannot enter itself", .Dst.AbsID())
			continue
		}
	}

}

func ( *compiler) ( *d2graph.Graph) {
	for ,  := range .Objects {
		for ,  := range []*d2graph.Scalar{.Top, .Left} {
			if  != nil {
				if .Parent != nil {
					if strings.EqualFold(.Parent.Shape.Value, d2target.ShapeHierarchy) {
						.errorf(.MapKey, `position keywords cannot be used with shape "hierarchy"`)
					}
					if .OuterSequenceDiagram() != nil {
						.errorf(.MapKey, `position keywords cannot be used inside shape "sequence_diagram"`)
					}
					if .Parent.GridColumns != nil || .Parent.GridRows != nil {
						.errorf(.MapKey, `position keywords cannot be used with grids`)
					}
				}
			}
		}
	}
}

func ( *compiler) ( *d2graph.Graph) {
	for ,  := range .Edges {
		// edges from a grid to something outside is ok
		//   grid -> outside : ok
		//   grid -> grid.cell : not ok
		//   grid -> grid.cell.inner : not ok
		if .Src.IsGridDiagram() && .Dst.IsDescendantOf(.Src) {
			.errorf(.GetAstEdge(), "edge from grid diagram %#v cannot enter itself", .Src.AbsID())
			continue
		}
		if .Dst.IsGridDiagram() && .Src.IsDescendantOf(.Dst) {
			.errorf(.GetAstEdge(), "edge from grid diagram %#v cannot enter itself", .Dst.AbsID())
			continue
		}
		if .Src.Parent.IsGridDiagram() && .Dst.IsDescendantOf(.Src) {
			.errorf(.GetAstEdge(), "edge from grid cell %#v cannot enter itself", .Src.AbsID())
			continue
		}
		if .Dst.Parent.IsGridDiagram() && .Src.IsDescendantOf(.Dst) {
			.errorf(.GetAstEdge(), "edge from grid cell %#v cannot enter itself", .Dst.AbsID())
			continue
		}
		if .Src.IsSequenceDiagram() && .Dst.IsDescendantOf(.Src) {
			.errorf(.GetAstEdge(), "edge from sequence diagram %#v cannot enter itself", .Src.AbsID())
			continue
		}
		if .Dst.IsSequenceDiagram() && .Src.IsDescendantOf(.Dst) {
			.errorf(.GetAstEdge(), "edge from sequence diagram %#v cannot enter itself", .Dst.AbsID())
			continue
		}
	}
}

func ( *compiler) ( *d2graph.Graph) {
	for ,  := range .Objects {
		if .Link == nil {
			continue
		}

		,  := d2parser.ParseKey(.Link.Value)
		if  != nil {
			continue
		}

		,  := url.Parse(html.UnescapeString(.Link.Value))
		 :=  == nil && .Scheme != ""
		if  {
			continue
		}

		if .Path[0].Unbox().ScalarString() != "root" {
			.Link = nil
			continue
		}

		if !hasBoard(.RootBoard(), .IDA()) {
			.Link = nil
			continue
		}

		if slices.Equal(.StringIDA(), .Graph.IDA()) {
			.Link = nil
			continue
		}
	}
	for ,  := range .Layers {
		.()
	}
	for ,  := range .Scenarios {
		.()
	}
	for ,  := range .Steps {
		.()
	}
}

func hasBoard( *d2graph.Graph,  []d2ast.String) bool {
	if len() == 0 {
		return true
	}
	if [0].ScalarString() == "root" && [0].IsUnquoted() {
		return (, [1:])
	}
	 := [0]
	if len() == 1 {
		return .Name == .ScalarString()
	}
	 := [1]
	switch .ScalarString() {
	case "layers":
		for ,  := range .Layers {
			if .Name == .ScalarString() {
				return (, [2:])
			}
		}
	case "scenarios":
		for ,  := range .Scenarios {
			if .Name == .ScalarString() {
				return (, [2:])
			}
		}
	case "steps":
		for ,  := range .Steps {
			if .Name == .ScalarString() {
				return (, [2:])
			}
		}
	}
	return false
}

func init() {
	FullToShortLanguageAliases = make(map[string]string, len(ShortToFullLanguageAliases))
	for ,  := range ShortToFullLanguageAliases {
		FullToShortLanguageAliases[] = 
	}
}

// Unused for now until shape: edge_group
func ( *compiler) ( *d2ir.Map) {
	for ,  := range .Fields {
		if .Name.ScalarString() == "shape" && .Name.IsUnquoted() && .Primary_.Value.ScalarString() == d2target.ShapeSequenceDiagram {
			.preprocessEdgeGroup(, )
			return
		}
		if .Map() != nil {
			.(.Map())
		}
	}
}

func ( *compiler) (,  *d2ir.Map) {
	// Any child of a sequence diagram can be either an actor, edge group or a span.
	// 1. Actors are shapes without edges inside them defined at the top level scope of a
	//    sequence diagram.
	// 2. Spans are the children of actors. For our purposes we can ignore them.
	// 3. Edge groups are defined as having at least one connection within them and also not
	//    being connected to anything. All direct children of an edge group are either edge
	//    groups or top level actors.

	// Go through all the fields and hoist actors from edge groups while also processing
	// the edge groups recursively.
	for ,  := range .Fields {
		if isEdgeGroup() {
			if .Map() != nil {
				.(, .Map())
			}
		} else {
			if  ==  {
				// Ignore for root.
				continue
			}
			hoistActor(, )
		}
	}

	// We need to adjust all edges recursively to point to actual actors instead.
	for ,  := range .Edges {
		if isCrossEdgeGroupEdge(, ) {
			.errorf(.References[0].AST(), "illegal edge between edge groups")
			continue
		}

		if  ==  {
			// Root edges between actors directly do not require hoisting.
			continue
		}

		 := 
		for ,  := range .ID.SrcPath {
			 := .GetField()
			if !isEdgeGroup() {
				for  := 0;  < +1; ++ {
					.ID.SrcPath = append([]d2ast.String{d2ast.FlatUnquotedString("_")}, .ID.SrcPath...)
					.ID.DstPath = append([]d2ast.String{d2ast.FlatUnquotedString("_")}, .ID.DstPath...)
				}
				break
			}
			 = .Map()
		}
	}
}

func hoistActor( *d2ir.Map,  *d2ir.Field) {
	 := .GetField(.Name)
	if  == nil {
		.Fields = append(.Fields, .Copy().(*d2ir.Field))
	} else {
		d2ir.OverlayField(, )
		d2ir.ParentMap().DeleteField(.Name.ScalarString())
	}
}

func isCrossEdgeGroupEdge( *d2ir.Map,  *d2ir.Edge) bool {
	 := 
	for ,  := range .ID.SrcPath {
		 := .GetField()
		if  == nil {
			// Hoisted already.
			break
		}
		if isEdgeGroup() {
			return true
		}
		 = .Map()
	}

	 := 
	for ,  := range .ID.DstPath {
		 := .GetField()
		if  == nil {
			// Hoisted already.
			break
		}
		if isEdgeGroup() {
			return true
		}
		 = .Map()
	}

	return false
}

func isEdgeGroup( d2ir.Node) bool {
	return .Map().EdgeCountRecursive() > 0
}

func parentSeqDiagram( d2ir.Node) *d2ir.Map {
	for {
		 := d2ir.ParentMap()
		if  == nil {
			return nil
		}
		for ,  := range .Fields {
			if .Name.ScalarString() == "shape" && .Name.IsUnquoted() && .Primary_.Value.ScalarString() == d2target.ShapeSequenceDiagram {
				return 
			}
		}
		 = 
	}
}

func compileConfig( *d2ir.Map) (*d2target.Config, error) {
	 := .GetField(d2ast.FlatUnquotedString("vars"), d2ast.FlatUnquotedString("d2-config"))
	if  == nil || .Map() == nil {
		return nil, nil
	}

	 := .Map()

	 := &d2target.Config{}

	 = .GetField(d2ast.FlatUnquotedString("sketch"))
	if  != nil {
		,  := strconv.ParseBool(.Primary().Value.ScalarString())
		.Sketch = &
	}

	 = .GetField(d2ast.FlatUnquotedString("theme-id"))
	if  != nil {
		,  := strconv.Atoi(.Primary().Value.ScalarString())
		.ThemeID = go2.Pointer(int64())
	}

	 = .GetField(d2ast.FlatUnquotedString("dark-theme-id"))
	if  != nil {
		,  := strconv.Atoi(.Primary().Value.ScalarString())
		.DarkThemeID = go2.Pointer(int64())
	}

	 = .GetField(d2ast.FlatUnquotedString("pad"))
	if  != nil {
		,  := strconv.Atoi(.Primary().Value.ScalarString())
		.Pad = go2.Pointer(int64())
	}

	 = .GetField(d2ast.FlatUnquotedString("layout-engine"))
	if  != nil {
		.LayoutEngine = go2.Pointer(.Primary().Value.ScalarString())
	}

	 = .GetField(d2ast.FlatUnquotedString("theme-overrides"))
	if  != nil {
		,  := compileThemeOverrides(.Map())
		if  != nil {
			return nil, 
		}
		.ThemeOverrides = 
	}
	 = .GetField(d2ast.FlatUnquotedString("dark-theme-overrides"))
	if  != nil {
		,  := compileThemeOverrides(.Map())
		if  != nil {
			return nil, 
		}
		.DarkThemeOverrides = 
	}
	 = .GetField(d2ast.FlatUnquotedString("data"))
	if  != nil && .Map() != nil {
		.Data = make(map[string]interface{})
		for ,  := range .Map().Fields {
			if .Primary() != nil {
				.Data[.Name.ScalarString()] = .Primary().Value.ScalarString()
			} else if .Composite != nil {
				var  []interface{}
				switch c := .Composite.(type) {
				case *d2ir.Array:
					for ,  := range .Values {
						switch c := .(type) {
						case *d2ir.Scalar:
							 = append(, .String())
						}
					}
				}
				.Data[.Name.ScalarString()] = 
			}
		}
	}

	return , nil
}

func compileThemeOverrides( *d2ir.Map) (*d2target.ThemeOverrides, error) {
	if  == nil {
		return nil, nil
	}
	 := d2target.ThemeOverrides{}

	 := &d2parser.ParseError{}
:
	for ,  := range .Fields {
		switch strings.ToUpper(.Name.ScalarString()) {
		case "N1":
			.N1 = go2.Pointer(.Primary().Value.ScalarString())
		case "N2":
			.N2 = go2.Pointer(.Primary().Value.ScalarString())
		case "N3":
			.N3 = go2.Pointer(.Primary().Value.ScalarString())
		case "N4":
			.N4 = go2.Pointer(.Primary().Value.ScalarString())
		case "N5":
			.N5 = go2.Pointer(.Primary().Value.ScalarString())
		case "N6":
			.N6 = go2.Pointer(.Primary().Value.ScalarString())
		case "N7":
			.N7 = go2.Pointer(.Primary().Value.ScalarString())
		case "B1":
			.B1 = go2.Pointer(.Primary().Value.ScalarString())
		case "B2":
			.B2 = go2.Pointer(.Primary().Value.ScalarString())
		case "B3":
			.B3 = go2.Pointer(.Primary().Value.ScalarString())
		case "B4":
			.B4 = go2.Pointer(.Primary().Value.ScalarString())
		case "B5":
			.B5 = go2.Pointer(.Primary().Value.ScalarString())
		case "B6":
			.B6 = go2.Pointer(.Primary().Value.ScalarString())
		case "AA2":
			.AA2 = go2.Pointer(.Primary().Value.ScalarString())
		case "AA4":
			.AA4 = go2.Pointer(.Primary().Value.ScalarString())
		case "AA5":
			.AA5 = go2.Pointer(.Primary().Value.ScalarString())
		case "AB4":
			.AB4 = go2.Pointer(.Primary().Value.ScalarString())
		case "AB5":
			.AB5 = go2.Pointer(.Primary().Value.ScalarString())
		default:
			.Errors = append(.Errors, d2parser.Errorf(.LastPrimaryKey(), fmt.Sprintf(`"%s" is not a valid theme code`, .Name.ScalarString())).(d2ast.Error))
			continue 
		}
		if !go2.Contains(color.NamedColors, strings.ToLower(.Primary().Value.ScalarString())) && !color.ColorHexRegex.MatchString(.Primary().Value.ScalarString()) {
			.Errors = append(.Errors, d2parser.Errorf(.LastPrimaryKey(), fmt.Sprintf(`expected "%s" to be a valid named color ("orange") or a hex code ("#f0ff3a")`, .Name.ScalarString())).(d2ast.Error))
		}
	}

	if !.Empty() {
		return nil, 
	}

	if  != (d2target.ThemeOverrides{}) {
		return &, nil
	}
	return nil, nil
}