// TODO: Remove boxes and cleanup like d2ir // // d2ast implements the d2 language's abstract syntax tree. // // Special characters to think about in parser: // # // """ // ; // [] // {} // | // $ // ' // " // \ // : // . // -- // <> // * // & // ()
package d2ast import ( ) // Node is the base interface implemented by all d2 AST nodes. // TODO: add error node for autofmt of incomplete AST type Node interface { node() // Type returns the user friendly name of the node. Type() string // GetRange returns the range a node occupies in its file. GetRange() Range Children() []Node } var _ Node = &Comment{} var _ Node = &BlockComment{} var _ Node = &Null{} var _ Node = &Boolean{} var _ Node = &Number{} var _ Node = &UnquotedString{} var _ Node = &DoubleQuotedString{} var _ Node = &SingleQuotedString{} var _ Node = &BlockString{} var _ Node = &Substitution{} var _ Node = &Import{} var _ Node = &Array{} var _ Node = &Map{} var _ Node = &Key{} var _ Node = &KeyPath{} var _ Node = &Edge{} var _ Node = &EdgeIndex{} // Range represents a range between Start and End in Path. // It's also used in the d2parser package to represent the range of an error. // // note: See docs on Position. // // It has a custom JSON string encoding with encoding.TextMarshaler and // encoding.TextUnmarshaler to keep it compact as the JSON struct encoding is too verbose, // especially with json.MarshalIndent. // // It looks like path,start-end type Range struct { Path string Start Position End Position } var _ fmt.Stringer = Range{} var _ encoding.TextMarshaler = Range{} var _ encoding.TextUnmarshaler = &Range{} func ( string) Range { var Range _ = .UnmarshalText([]byte()) return } // String returns a string representation of the range including only the path and start. // // If path is empty, it will be omitted. // // The format is path:start func ( Range) () string { var strings.Builder if .Path != "" { .WriteString(.Path) .WriteByte(':') } .WriteString(.Start.String()) return .String() } // OneLine returns true if the Range starts and ends on the same line. func ( Range) () bool { return .Start.Line == .End.Line } // See docs on Range. func ( Range) () ([]byte, error) { , := .Start.MarshalText() , := .End.MarshalText() return []byte(fmt.Sprintf("%s,%s-%s", .Path, , )), nil } // See docs on Range. func ( *Range) ( []byte) ( error) { defer xdefer.Errorf(&, "failed to unmarshal Range from %q", ) := bytes.LastIndexByte(, '-') if == -1 { return errors.New("missing End field") } := [+1:] = [:] = bytes.LastIndexByte(, ',') if == -1 { return errors.New("missing Start field") } := [+1:] = [:] .Path = string() = .Start.UnmarshalText() if != nil { return } return .End.UnmarshalText() } func ( Range) ( Range) bool { return .Start.Before(.Start) } // Position represents a line:column and byte position in a file. // // note: Line and Column are zero indexed. // note: Column and Byte are UTF-8 byte indexes unless byUTF16 was passed to Position.Advance in // . which they are UTF-16 code unit indexes. // . If intended for Javascript consumption like in the browser or via LSP, byUTF16 is // . set to true. type Position struct { Line int Column int // -1 is used as sentinel that a constructed position is missing byte offset (for LSP usage) Byte int } var _ fmt.Stringer = Position{} var _ encoding.TextMarshaler = Position{} var _ encoding.TextUnmarshaler = &Position{} // String returns a line:column representation of the position suitable for error messages. // // note: Should not normally be used directly, see Range.String() func ( Position) () string { return fmt.Sprintf("%d:%d", .Line+1, .Column+1) } func ( Position) () string { return fmt.Sprintf("%d:%d:%d", .Line, .Column, .Byte) } // See docs on Range. func ( Position) () ([]byte, error) { return []byte(fmt.Sprintf("%d:%d:%d", .Line, .Column, .Byte)), nil } // See docs on Range. func ( *Position) ( []byte) ( error) { defer xdefer.Errorf(&, "failed to unmarshal Position from %q", ) := bytes.Split(, []byte{':'}) if len() != 3 { return errors.New("expected three fields") } .Line, = strconv.Atoi(string([0])) if != nil { return } .Column, = strconv.Atoi(string([1])) if != nil { return } .Byte, = strconv.Atoi(string([2])) return } // From copies src into p. It's used in the d2parser package to set a node's Range.End to // the parser's current pos on all return paths with defer. func ( *Position) ( *Position) { * = * } // Advance advances p's Line, Column and Byte by r and returns // the new Position. // Set byUTF16 to advance the position as though r represents // a UTF-16 codepoint. func ( Position) ( rune, bool) Position { := utf8.RuneLen() if { = 1 , := utf16.EncodeRune() if != '\uFFFD' && != '\uFFFD' { = 2 } } if == '\n' { .Line++ .Column = 0 } else { .Column += } .Byte += return } func ( Position) ( rune, bool) Position { := utf8.RuneLen() if { = 1 , := utf16.EncodeRune() if != '\uFFFD' && != '\uFFFD' { = 2 } } if == '\n' { panic("d2ast: cannot subtract newline from Position") } else { .Column -= } .Byte -= return } func ( Position) ( string, bool) Position { for , := range { = .Advance(, ) } return } func ( Position) ( string, bool) Position { for , := range { = .Subtract(, ) } return } func ( Position) ( Position) bool { if .Byte != .Byte && .Byte != -1 && .Byte != -1 { return .Byte < .Byte } if .Line != .Line { return .Line < .Line } return .Column < .Column } // MapNode is implemented by nodes that may be children of Maps. type MapNode interface { Node mapNode() } var _ MapNode = &Comment{} var _ MapNode = &BlockComment{} var _ MapNode = &Key{} var _ MapNode = &Substitution{} var _ MapNode = &Import{} // ArrayNode is implemented by nodes that may be children of Arrays. type ArrayNode interface { Node arrayNode() } // See Value for the rest. var _ ArrayNode = &Comment{} var _ ArrayNode = &BlockComment{} var _ ArrayNode = &Substitution{} var _ ArrayNode = &Import{} // Value is implemented by nodes that may be values of a key. type Value interface { ArrayNode value() } // See Scalar for rest. var _ Value = &Array{} var _ Value = &Map{} // Scalar is implemented by nodes that represent scalar values. type Scalar interface { Value scalar() ScalarString() string } // See String for rest. var _ Scalar = &Null{} var _ Scalar = &Boolean{} var _ Scalar = &Number{} // String is implemented by nodes that represent strings. type String interface { Scalar SetString(string) Copy() String _string() IsUnquoted() bool } var _ String = &UnquotedString{} var _ String = &SingleQuotedString{} var _ String = &DoubleQuotedString{} var _ String = &BlockString{} func ( *Comment) () {} func ( *BlockComment) () {} func ( *Null) () {} func ( *Boolean) () {} func ( *Number) () {} func ( *UnquotedString) () {} func ( *DoubleQuotedString) () {} func ( *SingleQuotedString) () {} func ( *BlockString) () {} func ( *Substitution) () {} func ( *Import) () {} func ( *Array) () {} func ( *Map) () {} func ( *Key) () {} func ( *KeyPath) () {} func ( *Edge) () {} func ( *EdgeIndex) () {} func ( *Comment) () string { return "comment" } func ( *BlockComment) () string { return "block comment" } func ( *Null) () string { return "null" } func ( *Boolean) () string { return "boolean" } func ( *Number) () string { return "number" } func ( *UnquotedString) () string { return "unquoted string" } func ( *DoubleQuotedString) () string { return "double quoted string" } func ( *SingleQuotedString) () string { return "single quoted string" } func ( *BlockString) () string { return .Tag + " block string" } func ( *Substitution) () string { return "substitution" } func ( *Import) () string { return "import" } func ( *Array) () string { return "array" } func ( *Map) () string { return "map" } func ( *Key) () string { return "map key" } func ( *KeyPath) () string { return "key path" } func ( *Edge) () string { return "edge" } func ( *EdgeIndex) () string { return "edge index" } func ( *Comment) () Range { return .Range } func ( *BlockComment) () Range { return .Range } func ( *Null) () Range { return .Range } func ( *Boolean) () Range { return .Range } func ( *Number) () Range { return .Range } func ( *UnquotedString) () Range { return .Range } func ( *DoubleQuotedString) () Range { return .Range } func ( *SingleQuotedString) () Range { return .Range } func ( *BlockString) () Range { return .Range } func ( *Substitution) () Range { return .Range } func ( *Import) () Range { return .Range } func ( *Array) () Range { return .Range } func ( *Map) () Range { return .Range } func ( *Key) () Range { return .Range } func ( *KeyPath) () Range { return .Range } func ( *Edge) () Range { return .Range } func ( *EdgeIndex) () Range { return .Range } func ( *Comment) () {} func ( *BlockComment) () {} func ( *Key) () {} func ( *Substitution) () {} func ( *Import) () {} func ( *Comment) () {} func ( *BlockComment) () {} func ( *Null) () {} func ( *Boolean) () {} func ( *Number) () {} func ( *UnquotedString) () {} func ( *DoubleQuotedString) () {} func ( *SingleQuotedString) () {} func ( *BlockString) () {} func ( *Substitution) () {} func ( *Import) () {} func ( *Array) () {} func ( *Map) () {} func ( *Null) () {} func ( *Boolean) () {} func ( *Number) () {} func ( *UnquotedString) () {} func ( *DoubleQuotedString) () {} func ( *SingleQuotedString) () {} func ( *BlockString) () {} func ( *Array) () {} func ( *Map) () {} func ( *Import) () {} func ( *Null) () {} func ( *Boolean) () {} func ( *Number) () {} func ( *UnquotedString) () {} func ( *DoubleQuotedString) () {} func ( *SingleQuotedString) () {} func ( *BlockString) () {} func ( *Comment) () []Node { return nil } func ( *BlockComment) () []Node { return nil } func ( *Null) () []Node { return nil } func ( *Boolean) () []Node { return nil } func ( *Number) () []Node { return nil } func ( *SingleQuotedString) () []Node { return nil } func ( *BlockString) () []Node { return nil } func ( *EdgeIndex) () []Node { return nil } func ( *UnquotedString) () []Node { var []Node for , := range .Value { if .Substitution != nil { = append(, .Substitution) } } return } func ( *DoubleQuotedString) () []Node { var []Node for , := range .Value { if .Substitution != nil { = append(, .Substitution) } } return } func ( *Substitution) () []Node { var []Node for , := range .Path { if != nil { if := .Unbox(); != nil { = append(, ) } } } return } func ( *Import) () []Node { var []Node for , := range .Path { if != nil { if := .Unbox(); != nil { = append(, ) } } } return } func ( *Array) () []Node { var []Node for , := range .Nodes { if := .Unbox(); != nil { = append(, ) } } return } func ( *Map) () []Node { var []Node for , := range .Nodes { if := .Unbox(); != nil { = append(, ) } } return } func ( *Key) () []Node { var []Node if .Key != nil { = append(, .Key) } for , := range .Edges { if != nil { = append(, ) } } if .EdgeIndex != nil { = append(, .EdgeIndex) } if .EdgeKey != nil { = append(, .EdgeKey) } if := .Primary.Unbox(); != nil { = append(, ) } if := .Value.Unbox(); != nil { = append(, ) } return } func ( *KeyPath) () []Node { var []Node for , := range .Path { if != nil { if := .Unbox(); != nil { = append(, ) } } } return } func ( *Edge) () []Node { var []Node if .Src != nil { = append(, .Src) } if .Dst != nil { = append(, .Dst) } return } func ( Node, func(Node) bool) { if == nil { return } if !() { return } for , := range .Children() { (, ) } } // TODO: mistake, move into parse.go func ( *Null) () string { return "" } func ( *Boolean) () string { return strconv.FormatBool(.Value) } func ( *Number) () string { return .Raw } func ( *UnquotedString) () string { if len(.Value) == 0 { return "" } if .Value[0].String == nil { return "" } return *.Value[0].String } func ( *DoubleQuotedString) () string { if len(.Value) == 0 { return "" } if .Value[0].String == nil { return "" } return *.Value[0].String } func ( *SingleQuotedString) () string { return .Value } func ( *BlockString) () string { return .Value } func ( *UnquotedString) ( string) { .Value = []InterpolationBox{{String: &}} } func ( *DoubleQuotedString) ( string) { .Value = []InterpolationBox{{String: &}} } func ( *SingleQuotedString) ( string) { .Raw = ""; .Value = } func ( *BlockString) ( string) { .Value = } func ( *UnquotedString) () String { := *; return & } func ( *DoubleQuotedString) () String { := *; return & } func ( *SingleQuotedString) () String { := *; return & } func ( *BlockString) () String { := *; return & } func ( *UnquotedString) () {} func ( *DoubleQuotedString) () {} func ( *SingleQuotedString) () {} func ( *BlockString) () {} func ( *UnquotedString) () bool { return true } func ( *DoubleQuotedString) () bool { return false } func ( *SingleQuotedString) () bool { return false } func ( *BlockString) () bool { return false } type Comment struct { Range Range `json:"range"` Value string `json:"value"` } type BlockComment struct { Range Range `json:"range"` Value string `json:"value"` } type Null struct { Range Range `json:"range"` } type Boolean struct { Range Range `json:"range"` Value bool `json:"value"` } type Number struct { Range Range `json:"range"` Raw string `json:"raw"` Value *big.Rat `json:"value"` } type UnquotedString struct { Range Range `json:"range"` Value []InterpolationBox `json:"value"` // Pattern holds the parsed glob pattern if in a key and the unquoted string represents a valid pattern. Pattern []string `json:"pattern,omitempty"` } func ( *UnquotedString) () { var strings.Builder for , := range .Value { if .String == nil { break } .WriteString(*.String) } .SetString(.String()) } func ( string) *UnquotedString { return &UnquotedString{ Value: []InterpolationBox{{String: &}}, } } type DoubleQuotedString struct { Range Range `json:"range"` Value []InterpolationBox `json:"value"` } func ( *DoubleQuotedString) () { var strings.Builder for , := range .Value { if .String == nil { break } .WriteString(*.String) } .SetString(.String()) } func ( string) *DoubleQuotedString { return &DoubleQuotedString{ Value: []InterpolationBox{{String: &}}, } } type SingleQuotedString struct { Range Range `json:"range"` Raw string `json:"raw"` Value string `json:"value"` } type BlockString struct { Range Range `json:"range"` // Quote contains the pipe delimiter for the block string. // e.g. if 5 pipes were used to begin a block string, then Quote == "||||". // The tag is not included. Quote string `json:"quote"` Tag string `json:"tag"` Value string `json:"value"` } type Array struct { Range Range `json:"range"` Nodes []ArrayNodeBox `json:"nodes"` } type Map struct { Range Range `json:"range"` Nodes []MapNodeBox `json:"nodes"` } func ( *Map) (, MapNode) { := len(.Nodes) - 1 for , := range .Nodes { if .Unbox() == { = } } := make([]MapNodeBox, 0, len(.Nodes)) = append(, .Nodes[:+1]...) = append(, MakeMapNodeBox()) = append(, .Nodes[+1:]...) .Nodes = } func ( *Map) (, MapNode) { := len(.Nodes) for , := range .Nodes { if .Unbox() == { = } } := make([]MapNodeBox, 0, len(.Nodes)) = append(, .Nodes[:]...) = append(, MakeMapNodeBox()) = append(, .Nodes[:]...) .Nodes = } func ( *Map) () bool { return .Range.Start.Line == 0 && .Range.Start.Column == 0 } func ( *Map) () bool { for , := range .Nodes { if .MapKey != nil && (.MapKey.Ampersand || .MapKey.NotAmpersand) { return true } } return false } // TODO: require @ on import values for readability type Key struct { Range Range `json:"range"` // Indicates this MapKey is a filter selector. Ampersand bool `json:"ampersand,omitempty"` // Indicates this MapKey is a not filter selector. NotAmpersand bool `json:"not_ampersand,omitempty"` // At least one of Key and Edges will be set but all four can also be set. // The following are all valid MapKeys: // Key: // x // Edges: // x -> y // Edges and EdgeIndex: // (x -> y)[*] // Edges and EdgeKey: // (x -> y).label // Key and Edges: // container.(x -> y) // Key, Edges and EdgeKey: // container.(x -> y -> z).label // Key, Edges, EdgeIndex EdgeKey: // container.(x -> y -> z)[4].label Key *KeyPath `json:"key,omitempty"` Edges []*Edge `json:"edges,omitempty"` EdgeIndex *EdgeIndex `json:"edge_index,omitempty"` EdgeKey *KeyPath `json:"edge_key,omitempty"` Primary ScalarBox `json:"primary,omitempty"` Value ValueBox `json:"value"` } func ( *Key) ( *Key) bool { if == nil && == nil { return true } if ( == nil) || ( == nil) { return false } if .Ampersand != .Ampersand { return false } if .NotAmpersand != .NotAmpersand { return false } if (.Key == nil) != (.Key == nil) { return false } if (.EdgeIndex == nil) != (.EdgeIndex == nil) { return false } if (.EdgeKey == nil) != (.EdgeKey == nil) { return false } if len(.Edges) != len(.Edges) { return false } if (.Value.Map == nil) != (.Value.Map == nil) { if .Value.Map != nil && len(.Value.Map.Nodes) > 0 { return false } if .Value.Map != nil && len(.Value.Map.Nodes) > 0 { return false } } else if (.Value.Unbox() == nil) != (.Value.Unbox() == nil) { return false } if .Key != nil { if len(.Key.Path) != len(.Key.Path) { return false } for , := range .Key.Path { if .Unbox().ScalarString() != .Key.Path[].Unbox().ScalarString() { return false } } } if .EdgeKey != nil { if len(.EdgeKey.Path) != len(.EdgeKey.Path) { return false } for , := range .EdgeKey.Path { if .Unbox().ScalarString() != .EdgeKey.Path[].Unbox().ScalarString() { return false } } } if .Value.Map != nil && len(.Value.Map.Nodes) > 0 { if len(.Value.Map.Nodes) != len(.Value.Map.Nodes) { return false } for := range .Value.Map.Nodes { if !.Value.Map.Nodes[].MapKey.Equals(.Value.Map.Nodes[].MapKey) { return false } } } if .Value.Unbox() != nil { if (.Value.ScalarBox().Unbox() == nil) != (.Value.ScalarBox().Unbox() == nil) { return false } if .Value.ScalarBox().Unbox() != nil { if .Value.ScalarBox().Unbox().ScalarString() != .Value.ScalarBox().Unbox().ScalarString() { return false } } } return true } func ( *Key) ( *Key) bool { if == nil && == nil { return true } if ( == nil) || ( == nil) { return false } if .Ampersand != .Ampersand { return false } if .NotAmpersand != .NotAmpersand { return false } if (.Key == nil) != (.Key == nil) { return false } if (.EdgeIndex == nil) != (.EdgeIndex == nil) { return false } if .EdgeIndex != nil { if !.EdgeIndex.Equals(.EdgeIndex) { return false } } if (.EdgeKey == nil) != (.EdgeKey == nil) { return false } if len(.Edges) != len(.Edges) { return false } for := range .Edges { if !.Edges[].Equals(.Edges[]) { return false } } if (.Value.Map == nil) != (.Value.Map == nil) { if .Value.Map != nil && len(.Value.Map.Nodes) > 0 { return false } if .Value.Map != nil && len(.Value.Map.Nodes) > 0 { return false } } else if (.Value.Unbox() == nil) != (.Value.Unbox() == nil) { return false } if .Key != nil { if len(.Key.Path) != len(.Key.Path) { return false } for , := range .Key.Path { if .Unbox().ScalarString() != .Key.Path[].Unbox().ScalarString() { return false } } } if .EdgeKey != nil { if len(.EdgeKey.Path) != len(.EdgeKey.Path) { return false } for , := range .EdgeKey.Path { if .Unbox().ScalarString() != .EdgeKey.Path[].Unbox().ScalarString() { return false } } } if .Value.Map != nil && len(.Value.Map.Nodes) > 0 { if len(.Value.Map.Nodes) != len(.Value.Map.Nodes) { return false } for := range .Value.Map.Nodes { if !.Value.Map.Nodes[].MapKey.(.Value.Map.Nodes[].MapKey) { return false } } } if .Value.Unbox() != nil { if (.Value.ScalarBox().Unbox() == nil) != (.Value.ScalarBox().Unbox() == nil) { return false } if .Value.ScalarBox().Unbox() != nil { if .Value.ScalarBox().Unbox().ScalarString() != .Value.ScalarBox().Unbox().ScalarString() { return false } } } if .Primary.Unbox() != nil { if (.Primary.Unbox() == nil) != (.Primary.Unbox() == nil) { return false } if .Primary.ScalarString() != .Primary.ScalarString() { return false } } return true } func ( *Key) ( ScalarBox) { if .Value.Unbox() != nil && .Value.ScalarBox().Unbox() == nil { .Primary = } else { .Value = MakeValueBox(.Unbox()) } } func ( *Key) () bool { if .Key.HasGlob() { return true } for , := range .Edges { if .Src.HasGlob() || .Dst.HasGlob() { return true } } if .EdgeIndex != nil && .EdgeIndex.Glob { return true } if .EdgeKey.HasGlob() { return true } return false } func ( *Key) () bool { if .Key.HasTripleGlob() { return true } for , := range .Edges { if .Src.HasTripleGlob() || .Dst.HasTripleGlob() { return true } } if .EdgeKey.HasTripleGlob() { return true } return false } func ( *Key) () bool { if .Key.HasMultiGlob() { return true } for , := range .Edges { if .Src.HasMultiGlob() || .Dst.HasMultiGlob() { return true } } if .EdgeKey.HasMultiGlob() { return true } return false } func ( *Key) () bool { if .Key.HasGlob() && len(.Edges) == 0 { return true } if .EdgeIndex != nil && .EdgeIndex.Glob && .EdgeKey == nil { return true } if .EdgeKey.HasGlob() { return true } return false } func ( *Key) () *Key { := * return & } type KeyPath struct { Range Range `json:"range"` Path []*StringBox `json:"path"` } func ( []string) *KeyPath { := &KeyPath{} for , := range { .Path = append(.Path, MakeValueBox(RawString(, true)).StringBox()) } return } func ( []String) *KeyPath { := &KeyPath{} for , := range { .Path = append(.Path, MakeValueBox(RawString(.ScalarString(), true)).StringBox()) } return } func ( *KeyPath) () ( []String) { for , := range .Path { = append(, .Unbox()) } return } func ( *KeyPath) () ( []string) { for , := range .Path { = append(, .Unbox().ScalarString()) } return } func ( *KeyPath) () *KeyPath { := * .Path = nil .Path = append(.Path, .Path...) return & } func ( *KeyPath) () *StringBox { return .Path[len(.Path)-1] } func ( []string) bool { return len() == 3 && [0] == "*" && [1] == "" && [2] == "*" } func ( []string) bool { return len() == 5 && [0] == "*" && [1] == "" && [2] == "*" && [3] == "" && [4] == "*" } func ( *KeyPath) () bool { if == nil { return false } for , := range .Path { if .UnquotedString != nil && len(.UnquotedString.Pattern) > 0 { return true } } return false } func ( *KeyPath) () int { if == nil { return -1 } for , := range .Path { if .UnquotedString != nil && len(.UnquotedString.Pattern) > 0 { return } } return -1 } func ( *KeyPath) () bool { if == nil { return false } for , := range .Path { if .UnquotedString != nil && IsTripleGlob(.UnquotedString.Pattern) { return true } } return false } func ( *KeyPath) () bool { if == nil { return false } for , := range .Path { if .UnquotedString != nil && (IsDoubleGlob(.UnquotedString.Pattern) || IsTripleGlob(.UnquotedString.Pattern)) { return true } } return false } func ( *KeyPath) ( *KeyPath) bool { if len(.Path) != len(.Path) { return false } for , := range .Path { if .Unbox().ScalarString() != .Path[].Unbox().ScalarString() { return false } } return true } type Edge struct { Range Range `json:"range"` Src *KeyPath `json:"src"` // empty, < or * SrcArrow string `json:"src_arrow"` Dst *KeyPath `json:"dst"` // empty, > or * DstArrow string `json:"dst_arrow"` } func ( *Edge) ( *Edge) bool { if !.Src.Equals(.Src) { return false } if .SrcArrow != .SrcArrow { return false } if !.Dst.Equals(.Dst) { return false } if .DstArrow != .DstArrow { return false } return true } type EdgeIndex struct { Range Range `json:"range"` // [n] or [*] Int *int `json:"int"` Glob bool `json:"glob"` } func ( *EdgeIndex) ( *EdgeIndex) bool { // TODO probably should be checking the values, but will wait until something breaks to change if .Int != .Int { return false } if .Glob != .Glob { return false } return true } type Substitution struct { Range Range `json:"range"` Spread bool `json:"spread"` Path []*StringBox `json:"path"` } type Import struct { Range Range `json:"range"` Spread bool `json:"spread"` Pre string `json:"pre"` Path []*StringBox `json:"path"` } // MapNodeBox is used to box MapNode for JSON persistence. type MapNodeBox struct { Comment *Comment `json:"comment,omitempty"` BlockComment *BlockComment `json:"block_comment,omitempty"` Substitution *Substitution `json:"substitution,omitempty"` Import *Import `json:"import,omitempty"` MapKey *Key `json:"map_key,omitempty"` } func ( MapNode) MapNodeBox { var MapNodeBox switch n := .(type) { case *Comment: .Comment = case *BlockComment: .BlockComment = case *Substitution: .Substitution = case *Import: .Import = case *Key: .MapKey = } return } func ( MapNodeBox) () MapNode { switch { case .Comment != nil: return .Comment case .BlockComment != nil: return .BlockComment case .Substitution != nil: return .Substitution case .Import != nil: return .Import case .MapKey != nil: return .MapKey default: return nil } } func ( MapNodeBox) () bool { if .MapKey == nil || .MapKey.Key == nil || len(.MapKey.Key.Path) != 1 { return false } switch .MapKey.Key.Path[0].Unbox().ScalarString() { case "layers", "scenarios", "steps": return true default: return false } } // ArrayNodeBox is used to box ArrayNode for JSON persistence. type ArrayNodeBox struct { Comment *Comment `json:"comment,omitempty"` BlockComment *BlockComment `json:"block_comment,omitempty"` Substitution *Substitution `json:"substitution,omitempty"` Import *Import `json:"import,omitempty"` Null *Null `json:"null,omitempty"` Boolean *Boolean `json:"boolean,omitempty"` Number *Number `json:"number,omitempty"` UnquotedString *UnquotedString `json:"unquoted_string,omitempty"` DoubleQuotedString *DoubleQuotedString `json:"double_quoted_string,omitempty"` SingleQuotedString *SingleQuotedString `json:"single_quoted_string,omitempty"` BlockString *BlockString `json:"block_string,omitempty"` Array *Array `json:"array,omitempty"` Map *Map `json:"map,omitempty"` } func ( ArrayNode) ArrayNodeBox { var ArrayNodeBox switch an := .(type) { case *Comment: .Comment = case *BlockComment: .BlockComment = case *Substitution: .Substitution = case *Import: .Import = case *Null: .Null = case *Boolean: .Boolean = case *Number: .Number = case *UnquotedString: .UnquotedString = case *DoubleQuotedString: .DoubleQuotedString = case *SingleQuotedString: .SingleQuotedString = case *BlockString: .BlockString = case *Array: .Array = case *Map: .Map = } return } func ( ArrayNodeBox) () ArrayNode { switch { case .Comment != nil: return .Comment case .BlockComment != nil: return .BlockComment case .Substitution != nil: return .Substitution case .Import != nil: return .Import case .Null != nil: return .Null case .Boolean != nil: return .Boolean case .Number != nil: return .Number case .UnquotedString != nil: return .UnquotedString case .DoubleQuotedString != nil: return .DoubleQuotedString case .SingleQuotedString != nil: return .SingleQuotedString case .BlockString != nil: return .BlockString case .Array != nil: return .Array case .Map != nil: return .Map default: return nil } } // ValueBox is used to box Value for JSON persistence. type ValueBox struct { Null *Null `json:"null,omitempty"` Boolean *Boolean `json:"boolean,omitempty"` Number *Number `json:"number,omitempty"` UnquotedString *UnquotedString `json:"unquoted_string,omitempty"` DoubleQuotedString *DoubleQuotedString `json:"double_quoted_string,omitempty"` SingleQuotedString *SingleQuotedString `json:"single_quoted_string,omitempty"` BlockString *BlockString `json:"block_string,omitempty"` Array *Array `json:"array,omitempty"` Map *Map `json:"map,omitempty"` Import *Import `json:"import,omitempty"` } func ( ValueBox) () Value { switch { case .Null != nil: return .Null case .Boolean != nil: return .Boolean case .Number != nil: return .Number case .UnquotedString != nil: return .UnquotedString case .DoubleQuotedString != nil: return .DoubleQuotedString case .SingleQuotedString != nil: return .SingleQuotedString case .BlockString != nil: return .BlockString case .Array != nil: return .Array case .Map != nil: return .Map case .Import != nil: return .Import default: return nil } } func ( Value) ValueBox { var ValueBox switch v := .(type) { case *Null: .Null = case *Boolean: .Boolean = case *Number: .Number = case *UnquotedString: .UnquotedString = case *DoubleQuotedString: .DoubleQuotedString = case *SingleQuotedString: .SingleQuotedString = case *BlockString: .BlockString = case *Array: .Array = case *Map: .Map = case *Import: .Import = } return } func ( ValueBox) () ScalarBox { var ScalarBox .Null = .Null .Boolean = .Boolean .Number = .Number .UnquotedString = .UnquotedString .DoubleQuotedString = .DoubleQuotedString .SingleQuotedString = .SingleQuotedString .BlockString = .BlockString return } func ( ValueBox) () *StringBox { var StringBox .UnquotedString = .UnquotedString .DoubleQuotedString = .DoubleQuotedString .SingleQuotedString = .SingleQuotedString .BlockString = .BlockString return & } // ScalarBox is used to box Scalar for JSON persistence. // TODO: implement ScalarString() type ScalarBox struct { Null *Null `json:"null,omitempty"` Boolean *Boolean `json:"boolean,omitempty"` Number *Number `json:"number,omitempty"` UnquotedString *UnquotedString `json:"unquoted_string,omitempty"` DoubleQuotedString *DoubleQuotedString `json:"double_quoted_string,omitempty"` SingleQuotedString *SingleQuotedString `json:"single_quoted_string,omitempty"` BlockString *BlockString `json:"block_string,omitempty"` } func ( ScalarBox) () Scalar { switch { case .Null != nil: return .Null case .Boolean != nil: return .Boolean case .Number != nil: return .Number case .UnquotedString != nil: return .UnquotedString case .DoubleQuotedString != nil: return .DoubleQuotedString case .SingleQuotedString != nil: return .SingleQuotedString case .BlockString != nil: return .BlockString default: return nil } } func ( ScalarBox) () string { return .Unbox().ScalarString() } // StringBox is used to box String for JSON persistence. type StringBox struct { UnquotedString *UnquotedString `json:"unquoted_string,omitempty"` DoubleQuotedString *DoubleQuotedString `json:"double_quoted_string,omitempty"` SingleQuotedString *SingleQuotedString `json:"single_quoted_string,omitempty"` BlockString *BlockString `json:"block_string,omitempty"` } func ( *StringBox) () String { switch { case .UnquotedString != nil: return .UnquotedString case .DoubleQuotedString != nil: return .DoubleQuotedString case .SingleQuotedString != nil: return .SingleQuotedString case .BlockString != nil: return .BlockString default: return nil } } func ( *StringBox) () string { return .Unbox().ScalarString() } // InterpolationBox is used to select between strings and substitutions in unquoted and // double quoted strings. There is no corresponding interface to avoid unnecessary // abstraction. type InterpolationBox struct { String *string `json:"string,omitempty"` StringRaw *string `json:"raw_string,omitempty"` Substitution *Substitution `json:"substitution,omitempty"` } // & is only special if it begins a key. // - is only special if followed by another - in a key. // ' " and | are only special if they begin an unquoted key or value. var UnquotedKeySpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', ':', '.', '-', '<', '>', '*', '&', '(', ')', '@', '&'}) var UnquotedValueSpecials = string([]rune{'#', ';', '\n', '\\', '{', '}', '[', ']', '\'', '"', '|', '$', '@'}) // RawString returns s in a AST String node that can format s in the most aesthetically // pleasing way. func ( string, bool) String { if == "" { return FlatDoubleQuotedString() } if { for , := range { switch { case '-': if +1 < len() && [+1] != '-' { continue } } if strings.ContainsRune(UnquotedKeySpecials, ) { if !strings.ContainsRune(, '"') { return FlatDoubleQuotedString() } if strings.ContainsRune(, '\n') { return FlatDoubleQuotedString() } return &SingleQuotedString{Value: } } } } else if == "null" || strings.ContainsAny(, UnquotedValueSpecials) { if !strings.ContainsRune(, '"') && !strings.ContainsRune(, '$') { return FlatDoubleQuotedString() } if strings.ContainsRune(, '\n') { return FlatDoubleQuotedString() } return &SingleQuotedString{Value: } } if hasSurroundingWhitespace() { return FlatDoubleQuotedString() } return FlatUnquotedString() } func ( string, bool) *StringBox { return MakeValueBox(RawString(, )).StringBox() } func hasSurroundingWhitespace( string) bool { , := utf8.DecodeRuneInString() , := utf8.DecodeLastRuneInString() return unicode.IsSpace() || unicode.IsSpace() } func ( *Substitution) () ( []string) { for , := range .Path { = append(, .Unbox().ScalarString()) } return } func ( *Import) () ( []String) { for , := range .Path[1:] { = append(, .Unbox()) } return } func ( *Import) () string { if len(.Path) == 0 { return "" } return path.Join(.Pre, .Path[0].Unbox().ScalarString()) } func ( *Import) () string { return path.Dir(.PathWithPre()) }