d2/d2ast/d2ast.go
Alexander Wang 6456f1866e
test
2025-02-28 15:29:54 -07:00

1636 lines
40 KiB
Go

// 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 (
"bytes"
"encoding"
"errors"
"fmt"
"math/big"
"path"
"strconv"
"strings"
"unicode"
"unicode/utf16"
"unicode/utf8"
"oss.terrastruct.com/util-go/xdefer"
)
// 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 = &Suspension{}
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 MakeRange(s string) Range {
var r Range
_ = r.UnmarshalText([]byte(s))
return r
}
// 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 (r Range) String() string {
var s strings.Builder
if r.Path != "" {
s.WriteString(r.Path)
s.WriteByte(':')
}
s.WriteString(r.Start.String())
return s.String()
}
// OneLine returns true if the Range starts and ends on the same line.
func (r Range) OneLine() bool {
return r.Start.Line == r.End.Line
}
// See docs on Range.
func (r Range) MarshalText() ([]byte, error) {
start, _ := r.Start.MarshalText()
end, _ := r.End.MarshalText()
return []byte(fmt.Sprintf("%s,%s-%s", r.Path, start, end)), nil
}
// See docs on Range.
func (r *Range) UnmarshalText(b []byte) (err error) {
defer xdefer.Errorf(&err, "failed to unmarshal Range from %q", b)
i := bytes.LastIndexByte(b, '-')
if i == -1 {
return errors.New("missing End field")
}
end := b[i+1:]
b = b[:i]
i = bytes.LastIndexByte(b, ',')
if i == -1 {
return errors.New("missing Start field")
}
start := b[i+1:]
b = b[:i]
r.Path = string(b)
err = r.Start.UnmarshalText(start)
if err != nil {
return err
}
return r.End.UnmarshalText(end)
}
func (r Range) Before(r2 Range) bool {
return r.Start.Before(r2.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 (p Position) String() string {
return fmt.Sprintf("%d:%d", p.Line+1, p.Column+1)
}
func (p Position) Debug() string {
return fmt.Sprintf("%d:%d:%d", p.Line, p.Column, p.Byte)
}
// See docs on Range.
func (p Position) MarshalText() ([]byte, error) {
return []byte(fmt.Sprintf("%d:%d:%d", p.Line, p.Column, p.Byte)), nil
}
// See docs on Range.
func (p *Position) UnmarshalText(b []byte) (err error) {
defer xdefer.Errorf(&err, "failed to unmarshal Position from %q", b)
fields := bytes.Split(b, []byte{':'})
if len(fields) != 3 {
return errors.New("expected three fields")
}
p.Line, err = strconv.Atoi(string(fields[0]))
if err != nil {
return err
}
p.Column, err = strconv.Atoi(string(fields[1]))
if err != nil {
return err
}
p.Byte, err = strconv.Atoi(string(fields[2]))
return err
}
// 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 (p *Position) From(src *Position) {
*p = *src
}
// 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 (p Position) Advance(r rune, byUTF16 bool) Position {
size := utf8.RuneLen(r)
if byUTF16 {
size = 1
r1, r2 := utf16.EncodeRune(r)
if r1 != '\uFFFD' && r2 != '\uFFFD' {
size = 2
}
}
if r == '\n' {
p.Line++
p.Column = 0
} else {
p.Column += size
}
p.Byte += size
return p
}
func (p Position) Subtract(r rune, byUTF16 bool) Position {
size := utf8.RuneLen(r)
if byUTF16 {
size = 1
r1, r2 := utf16.EncodeRune(r)
if r1 != '\uFFFD' && r2 != '\uFFFD' {
size = 2
}
}
if r == '\n' {
panic("d2ast: cannot subtract newline from Position")
} else {
p.Column -= size
}
p.Byte -= size
return p
}
func (p Position) AdvanceString(s string, byUTF16 bool) Position {
for _, r := range s {
p = p.Advance(r, byUTF16)
}
return p
}
func (p Position) SubtractString(s string, byUTF16 bool) Position {
for _, r := range s {
p = p.Subtract(r, byUTF16)
}
return p
}
func (p Position) Before(p2 Position) bool {
if p.Byte != p2.Byte && p.Byte != -1 && p2.Byte != -1 {
return p.Byte < p2.Byte
}
if p.Line != p2.Line {
return p.Line < p2.Line
}
return p.Column < p2.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 = &Suspension{}
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 (c *Comment) node() {}
func (c *BlockComment) node() {}
func (n *Null) node() {}
func (n *Suspension) node() {}
func (b *Boolean) node() {}
func (n *Number) node() {}
func (s *UnquotedString) node() {}
func (s *DoubleQuotedString) node() {}
func (s *SingleQuotedString) node() {}
func (s *BlockString) node() {}
func (s *Substitution) node() {}
func (i *Import) node() {}
func (a *Array) node() {}
func (m *Map) node() {}
func (k *Key) node() {}
func (k *KeyPath) node() {}
func (e *Edge) node() {}
func (i *EdgeIndex) node() {}
func (c *Comment) Type() string { return "comment" }
func (c *BlockComment) Type() string { return "block comment" }
func (n *Null) Type() string { return "null" }
func (n *Suspension) Type() string { return "suspension" }
func (b *Boolean) Type() string { return "boolean" }
func (n *Number) Type() string { return "number" }
func (s *UnquotedString) Type() string { return "unquoted string" }
func (s *DoubleQuotedString) Type() string { return "double quoted string" }
func (s *SingleQuotedString) Type() string { return "single quoted string" }
func (s *BlockString) Type() string { return s.Tag + " block string" }
func (s *Substitution) Type() string { return "substitution" }
func (i *Import) Type() string { return "import" }
func (a *Array) Type() string { return "array" }
func (m *Map) Type() string { return "map" }
func (k *Key) Type() string { return "map key" }
func (k *KeyPath) Type() string { return "key path" }
func (e *Edge) Type() string { return "edge" }
func (i *EdgeIndex) Type() string { return "edge index" }
func (c *Comment) GetRange() Range { return c.Range }
func (c *BlockComment) GetRange() Range { return c.Range }
func (n *Null) GetRange() Range { return n.Range }
func (n *Suspension) GetRange() Range { return n.Range }
func (b *Boolean) GetRange() Range { return b.Range }
func (n *Number) GetRange() Range { return n.Range }
func (s *UnquotedString) GetRange() Range { return s.Range }
func (s *DoubleQuotedString) GetRange() Range { return s.Range }
func (s *SingleQuotedString) GetRange() Range { return s.Range }
func (s *BlockString) GetRange() Range { return s.Range }
func (s *Substitution) GetRange() Range { return s.Range }
func (i *Import) GetRange() Range { return i.Range }
func (a *Array) GetRange() Range { return a.Range }
func (m *Map) GetRange() Range { return m.Range }
func (k *Key) GetRange() Range { return k.Range }
func (k *KeyPath) GetRange() Range { return k.Range }
func (e *Edge) GetRange() Range { return e.Range }
func (i *EdgeIndex) GetRange() Range { return i.Range }
func (c *Comment) mapNode() {}
func (c *BlockComment) mapNode() {}
func (k *Key) mapNode() {}
func (s *Substitution) mapNode() {}
func (i *Import) mapNode() {}
func (c *Comment) arrayNode() {}
func (c *BlockComment) arrayNode() {}
func (n *Null) arrayNode() {}
func (n *Suspension) arrayNode() {}
func (b *Boolean) arrayNode() {}
func (n *Number) arrayNode() {}
func (s *UnquotedString) arrayNode() {}
func (s *DoubleQuotedString) arrayNode() {}
func (s *SingleQuotedString) arrayNode() {}
func (s *BlockString) arrayNode() {}
func (s *Substitution) arrayNode() {}
func (i *Import) arrayNode() {}
func (a *Array) arrayNode() {}
func (m *Map) arrayNode() {}
func (n *Null) value() {}
func (n *Suspension) value() {}
func (b *Boolean) value() {}
func (n *Number) value() {}
func (s *UnquotedString) value() {}
func (s *DoubleQuotedString) value() {}
func (s *SingleQuotedString) value() {}
func (s *BlockString) value() {}
func (a *Array) value() {}
func (m *Map) value() {}
func (i *Import) value() {}
func (n *Null) scalar() {}
func (n *Suspension) scalar() {}
func (b *Boolean) scalar() {}
func (n *Number) scalar() {}
func (s *UnquotedString) scalar() {}
func (s *DoubleQuotedString) scalar() {}
func (s *SingleQuotedString) scalar() {}
func (s *BlockString) scalar() {}
func (c *Comment) Children() []Node { return nil }
func (c *BlockComment) Children() []Node { return nil }
func (n *Null) Children() []Node { return nil }
func (n *Suspension) Children() []Node { return nil }
func (b *Boolean) Children() []Node { return nil }
func (n *Number) Children() []Node { return nil }
func (s *SingleQuotedString) Children() []Node { return nil }
func (s *BlockString) Children() []Node { return nil }
func (ei *EdgeIndex) Children() []Node { return nil }
func (s *UnquotedString) Children() []Node {
var children []Node
for _, box := range s.Value {
if box.Substitution != nil {
children = append(children, box.Substitution)
}
}
return children
}
func (s *DoubleQuotedString) Children() []Node {
var children []Node
for _, box := range s.Value {
if box.Substitution != nil {
children = append(children, box.Substitution)
}
}
return children
}
func (s *Substitution) Children() []Node {
var children []Node
for _, sb := range s.Path {
if sb != nil {
if child := sb.Unbox(); child != nil {
children = append(children, child)
}
}
}
return children
}
func (i *Import) Children() []Node {
var children []Node
for _, sb := range i.Path {
if sb != nil {
if child := sb.Unbox(); child != nil {
children = append(children, child)
}
}
}
return children
}
func (a *Array) Children() []Node {
var children []Node
for _, box := range a.Nodes {
if child := box.Unbox(); child != nil {
children = append(children, child)
}
}
return children
}
func (m *Map) Children() []Node {
var children []Node
for _, box := range m.Nodes {
if child := box.Unbox(); child != nil {
children = append(children, child)
}
}
return children
}
func (k *Key) Children() []Node {
var children []Node
if k.Key != nil {
children = append(children, k.Key)
}
for _, edge := range k.Edges {
if edge != nil {
children = append(children, edge)
}
}
if k.EdgeIndex != nil {
children = append(children, k.EdgeIndex)
}
if k.EdgeKey != nil {
children = append(children, k.EdgeKey)
}
if scalar := k.Primary.Unbox(); scalar != nil {
children = append(children, scalar)
}
if value := k.Value.Unbox(); value != nil {
children = append(children, value)
}
return children
}
func (kp *KeyPath) Children() []Node {
var children []Node
for _, sb := range kp.Path {
if sb != nil {
if child := sb.Unbox(); child != nil {
children = append(children, child)
}
}
}
return children
}
func (e *Edge) Children() []Node {
var children []Node
if e.Src != nil {
children = append(children, e.Src)
}
if e.Dst != nil {
children = append(children, e.Dst)
}
return children
}
func Walk(node Node, fn func(Node) bool) {
if node == nil {
return
}
if !fn(node) {
return
}
for _, child := range node.Children() {
Walk(child, fn)
}
}
// TODO: mistake, move into parse.go
func (n *Null) ScalarString() string { return "" }
func (n *Suspension) ScalarString() string { return "" }
func (b *Boolean) ScalarString() string { return strconv.FormatBool(b.Value) }
func (n *Number) ScalarString() string { return n.Raw }
func (s *UnquotedString) ScalarString() string {
if len(s.Value) == 0 {
return ""
}
if s.Value[0].String == nil {
return ""
}
return *s.Value[0].String
}
func (s *DoubleQuotedString) ScalarString() string {
if len(s.Value) == 0 {
return ""
}
if s.Value[0].String == nil {
return ""
}
return *s.Value[0].String
}
func (s *SingleQuotedString) ScalarString() string { return s.Value }
func (s *BlockString) ScalarString() string { return s.Value }
func (s *UnquotedString) SetString(s2 string) { s.Value = []InterpolationBox{{String: &s2}} }
func (s *DoubleQuotedString) SetString(s2 string) { s.Value = []InterpolationBox{{String: &s2}} }
func (s *SingleQuotedString) SetString(s2 string) { s.Raw = ""; s.Value = s2 }
func (s *BlockString) SetString(s2 string) { s.Value = s2 }
func (s *UnquotedString) Copy() String { tmp := *s; return &tmp }
func (s *DoubleQuotedString) Copy() String { tmp := *s; return &tmp }
func (s *SingleQuotedString) Copy() String { tmp := *s; return &tmp }
func (s *BlockString) Copy() String { tmp := *s; return &tmp }
func (s *UnquotedString) _string() {}
func (s *DoubleQuotedString) _string() {}
func (s *SingleQuotedString) _string() {}
func (s *BlockString) _string() {}
func (s *UnquotedString) IsUnquoted() bool { return true }
func (s *DoubleQuotedString) IsUnquoted() bool { return false }
func (s *SingleQuotedString) IsUnquoted() bool { return false }
func (s *BlockString) IsUnquoted() 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 Suspension struct {
Range Range `json:"range"`
Value bool `json:"value"`
}
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 (s *UnquotedString) Coalesce() {
var b strings.Builder
for _, box := range s.Value {
if box.String == nil {
break
}
b.WriteString(*box.String)
}
s.SetString(b.String())
}
func FlatUnquotedString(s string) *UnquotedString {
return &UnquotedString{
Value: []InterpolationBox{{String: &s}},
}
}
type DoubleQuotedString struct {
Range Range `json:"range"`
Value []InterpolationBox `json:"value"`
}
func (s *DoubleQuotedString) Coalesce() {
var b strings.Builder
for _, box := range s.Value {
if box.String == nil {
break
}
b.WriteString(*box.String)
}
s.SetString(b.String())
}
func FlatDoubleQuotedString(s string) *DoubleQuotedString {
return &DoubleQuotedString{
Value: []InterpolationBox{{String: &s}},
}
}
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 (m *Map) InsertAfter(cursor, n MapNode) {
afterIndex := len(m.Nodes) - 1
for i, n := range m.Nodes {
if n.Unbox() == cursor {
afterIndex = i
}
}
a := make([]MapNodeBox, 0, len(m.Nodes))
a = append(a, m.Nodes[:afterIndex+1]...)
a = append(a, MakeMapNodeBox(n))
a = append(a, m.Nodes[afterIndex+1:]...)
m.Nodes = a
}
func (m *Map) InsertBefore(cursor, n MapNode) {
beforeIndex := len(m.Nodes)
for i, n := range m.Nodes {
if n.Unbox() == cursor {
beforeIndex = i
}
}
a := make([]MapNodeBox, 0, len(m.Nodes))
a = append(a, m.Nodes[:beforeIndex]...)
a = append(a, MakeMapNodeBox(n))
a = append(a, m.Nodes[beforeIndex:]...)
m.Nodes = a
}
func (m *Map) IsFileMap() bool {
return m.Range.Start.Line == 0 && m.Range.Start.Column == 0
}
func (m *Map) HasFilter() bool {
for _, n := range m.Nodes {
if n.MapKey != nil && (n.MapKey.Ampersand || n.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 (mk1 *Key) D2OracleEquals(mk2 *Key) bool {
if mk1 == nil && mk2 == nil {
return true
}
if (mk1 == nil) || (mk2 == nil) {
return false
}
if mk1.Ampersand != mk2.Ampersand {
return false
}
if mk1.NotAmpersand != mk2.NotAmpersand {
return false
}
if (mk1.Key == nil) != (mk2.Key == nil) {
return false
}
if (mk1.EdgeIndex == nil) != (mk2.EdgeIndex == nil) {
return false
}
if (mk1.EdgeKey == nil) != (mk2.EdgeKey == nil) {
return false
}
if len(mk1.Edges) != len(mk2.Edges) {
return false
}
if (mk1.Value.Map == nil) != (mk2.Value.Map == nil) {
if mk1.Value.Map != nil && len(mk1.Value.Map.Nodes) > 0 {
return false
}
if mk2.Value.Map != nil && len(mk2.Value.Map.Nodes) > 0 {
return false
}
} else if (mk1.Value.Unbox() == nil) != (mk2.Value.Unbox() == nil) {
return false
}
if mk1.Key != nil {
if len(mk1.Key.Path) != len(mk2.Key.Path) {
return false
}
for i, id := range mk1.Key.Path {
if id.Unbox().ScalarString() != mk2.Key.Path[i].Unbox().ScalarString() {
return false
}
}
}
if mk1.EdgeKey != nil {
if len(mk1.EdgeKey.Path) != len(mk2.EdgeKey.Path) {
return false
}
for i, id := range mk1.EdgeKey.Path {
if id.Unbox().ScalarString() != mk2.EdgeKey.Path[i].Unbox().ScalarString() {
return false
}
}
}
if mk1.Value.Map != nil && len(mk1.Value.Map.Nodes) > 0 {
if len(mk1.Value.Map.Nodes) != len(mk2.Value.Map.Nodes) {
return false
}
for i := range mk1.Value.Map.Nodes {
if !mk1.Value.Map.Nodes[i].MapKey.Equals(mk2.Value.Map.Nodes[i].MapKey) {
return false
}
}
}
if mk1.Value.Unbox() != nil {
if (mk1.Value.ScalarBox().Unbox() == nil) != (mk2.Value.ScalarBox().Unbox() == nil) {
return false
}
if mk1.Value.ScalarBox().Unbox() != nil {
if mk1.Value.ScalarBox().Unbox().ScalarString() != mk2.Value.ScalarBox().Unbox().ScalarString() {
return false
}
}
}
return true
}
func (mk1 *Key) Equals(mk2 *Key) bool {
if mk1 == nil && mk2 == nil {
return true
}
if (mk1 == nil) || (mk2 == nil) {
return false
}
if mk1.Ampersand != mk2.Ampersand {
return false
}
if mk1.NotAmpersand != mk2.NotAmpersand {
return false
}
if (mk1.Key == nil) != (mk2.Key == nil) {
return false
}
if (mk1.EdgeIndex == nil) != (mk2.EdgeIndex == nil) {
return false
}
if mk1.EdgeIndex != nil {
if !mk1.EdgeIndex.Equals(mk2.EdgeIndex) {
return false
}
}
if (mk1.EdgeKey == nil) != (mk2.EdgeKey == nil) {
return false
}
if len(mk1.Edges) != len(mk2.Edges) {
return false
}
for i := range mk1.Edges {
if !mk1.Edges[i].Equals(mk2.Edges[i]) {
return false
}
}
if (mk1.Value.Map == nil) != (mk2.Value.Map == nil) {
if mk1.Value.Map != nil && len(mk1.Value.Map.Nodes) > 0 {
return false
}
if mk2.Value.Map != nil && len(mk2.Value.Map.Nodes) > 0 {
return false
}
} else if (mk1.Value.Unbox() == nil) != (mk2.Value.Unbox() == nil) {
return false
}
if mk1.Key != nil {
if len(mk1.Key.Path) != len(mk2.Key.Path) {
return false
}
for i, id := range mk1.Key.Path {
if id.Unbox().ScalarString() != mk2.Key.Path[i].Unbox().ScalarString() {
return false
}
}
}
if mk1.EdgeKey != nil {
if len(mk1.EdgeKey.Path) != len(mk2.EdgeKey.Path) {
return false
}
for i, id := range mk1.EdgeKey.Path {
if id.Unbox().ScalarString() != mk2.EdgeKey.Path[i].Unbox().ScalarString() {
return false
}
}
}
if mk1.Value.Map != nil && len(mk1.Value.Map.Nodes) > 0 {
if len(mk1.Value.Map.Nodes) != len(mk2.Value.Map.Nodes) {
return false
}
for i := range mk1.Value.Map.Nodes {
if !mk1.Value.Map.Nodes[i].MapKey.Equals(mk2.Value.Map.Nodes[i].MapKey) {
return false
}
}
}
if mk1.Value.Unbox() != nil {
if (mk1.Value.ScalarBox().Unbox() == nil) != (mk2.Value.ScalarBox().Unbox() == nil) {
return false
}
if mk1.Value.ScalarBox().Unbox() != nil {
if mk1.Value.ScalarBox().Unbox().ScalarString() != mk2.Value.ScalarBox().Unbox().ScalarString() {
return false
}
}
}
if mk1.Primary.Unbox() != nil {
if (mk1.Primary.Unbox() == nil) != (mk2.Primary.Unbox() == nil) {
return false
}
if mk1.Primary.ScalarString() != mk2.Primary.ScalarString() {
return false
}
}
return true
}
func (mk *Key) SetScalar(scalar ScalarBox) {
if mk.Value.Unbox() != nil && mk.Value.ScalarBox().Unbox() == nil {
mk.Primary = scalar
} else {
mk.Value = MakeValueBox(scalar.Unbox())
}
}
func (mk *Key) HasGlob() bool {
if mk.Key.HasGlob() {
return true
}
for _, e := range mk.Edges {
if e.Src.HasGlob() || e.Dst.HasGlob() {
return true
}
}
if mk.EdgeIndex != nil && mk.EdgeIndex.Glob {
return true
}
if mk.EdgeKey.HasGlob() {
return true
}
return false
}
func (mk *Key) HasTripleGlob() bool {
if mk.Key.HasTripleGlob() {
return true
}
for _, e := range mk.Edges {
if e.Src.HasTripleGlob() || e.Dst.HasTripleGlob() {
return true
}
}
if mk.EdgeKey.HasTripleGlob() {
return true
}
return false
}
func (mk *Key) HasMultiGlob() bool {
if mk.Key.HasMultiGlob() {
return true
}
for _, e := range mk.Edges {
if e.Src.HasMultiGlob() || e.Dst.HasMultiGlob() {
return true
}
}
if mk.EdgeKey.HasMultiGlob() {
return true
}
return false
}
func (mk *Key) SupportsGlobFilters() bool {
if mk.Key.HasGlob() && len(mk.Edges) == 0 {
return true
}
if mk.EdgeIndex != nil && mk.EdgeIndex.Glob && mk.EdgeKey == nil {
return true
}
if mk.EdgeKey.HasGlob() {
return true
}
return false
}
func (mk *Key) Copy() *Key {
mk2 := *mk
return &mk2
}
type KeyPath struct {
Range Range `json:"range"`
Path []*StringBox `json:"path"`
}
func MakeKeyPath(a []string) *KeyPath {
kp := &KeyPath{}
for _, el := range a {
kp.Path = append(kp.Path, MakeValueBox(RawString(el, true)).StringBox())
}
return kp
}
func MakeKeyPathString(a []String) *KeyPath {
kp := &KeyPath{}
for _, el := range a {
kp.Path = append(kp.Path, MakeValueBox(RawString(el.ScalarString(), true)).StringBox())
}
return kp
}
func (kp *KeyPath) IDA() (ida []String) {
for _, el := range kp.Path {
ida = append(ida, el.Unbox())
}
return ida
}
func (kp *KeyPath) StringIDA() (ida []string) {
for _, el := range kp.Path {
ida = append(ida, el.Unbox().ScalarString())
}
return ida
}
func (kp *KeyPath) Copy() *KeyPath {
kp2 := *kp
kp2.Path = nil
kp2.Path = append(kp2.Path, kp.Path...)
return &kp2
}
func (kp *KeyPath) Last() *StringBox {
return kp.Path[len(kp.Path)-1]
}
func IsDoubleGlob(pattern []string) bool {
return len(pattern) == 3 && pattern[0] == "*" && pattern[1] == "" && pattern[2] == "*"
}
func IsTripleGlob(pattern []string) bool {
return len(pattern) == 5 && pattern[0] == "*" && pattern[1] == "" && pattern[2] == "*" && pattern[3] == "" && pattern[4] == "*"
}
func (kp *KeyPath) HasGlob() bool {
if kp == nil {
return false
}
for _, el := range kp.Path {
if el.UnquotedString != nil && len(el.UnquotedString.Pattern) > 0 {
return true
}
}
return false
}
func (kp *KeyPath) FirstGlob() int {
if kp == nil {
return -1
}
for i, el := range kp.Path {
if el.UnquotedString != nil && len(el.UnquotedString.Pattern) > 0 {
return i
}
}
return -1
}
func (kp *KeyPath) HasTripleGlob() bool {
if kp == nil {
return false
}
for _, el := range kp.Path {
if el.UnquotedString != nil && IsTripleGlob(el.UnquotedString.Pattern) {
return true
}
}
return false
}
func (kp *KeyPath) HasMultiGlob() bool {
if kp == nil {
return false
}
for _, el := range kp.Path {
if el.UnquotedString != nil && (IsDoubleGlob(el.UnquotedString.Pattern) || IsTripleGlob(el.UnquotedString.Pattern)) {
return true
}
}
return false
}
func (kp1 *KeyPath) Equals(kp2 *KeyPath) bool {
if len(kp1.Path) != len(kp2.Path) {
return false
}
for i, id := range kp1.Path {
if id.Unbox().ScalarString() != kp2.Path[i].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 (e1 *Edge) Equals(e2 *Edge) bool {
if !e1.Src.Equals(e2.Src) {
return false
}
if e1.SrcArrow != e2.SrcArrow {
return false
}
if !e1.Dst.Equals(e2.Dst) {
return false
}
if e1.DstArrow != e2.DstArrow {
return false
}
return true
}
type EdgeIndex struct {
Range Range `json:"range"`
// [n] or [*]
Int *int `json:"int"`
Glob bool `json:"glob"`
}
func (ei1 *EdgeIndex) Equals(ei2 *EdgeIndex) bool {
// TODO probably should be checking the values, but will wait until something breaks to change
if ei1.Int != ei2.Int {
return false
}
if ei1.Glob != ei2.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 MakeMapNodeBox(n MapNode) MapNodeBox {
var box MapNodeBox
switch n := n.(type) {
case *Comment:
box.Comment = n
case *BlockComment:
box.BlockComment = n
case *Substitution:
box.Substitution = n
case *Import:
box.Import = n
case *Key:
box.MapKey = n
}
return box
}
func (mb MapNodeBox) Unbox() MapNode {
switch {
case mb.Comment != nil:
return mb.Comment
case mb.BlockComment != nil:
return mb.BlockComment
case mb.Substitution != nil:
return mb.Substitution
case mb.Import != nil:
return mb.Import
case mb.MapKey != nil:
return mb.MapKey
default:
return nil
}
}
func (mb MapNodeBox) IsBoardNode() bool {
if mb.MapKey == nil || mb.MapKey.Key == nil || len(mb.MapKey.Key.Path) != 1 {
return false
}
switch mb.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 MakeArrayNodeBox(an ArrayNode) ArrayNodeBox {
var ab ArrayNodeBox
switch an := an.(type) {
case *Comment:
ab.Comment = an
case *BlockComment:
ab.BlockComment = an
case *Substitution:
ab.Substitution = an
case *Import:
ab.Import = an
case *Null:
ab.Null = an
case *Boolean:
ab.Boolean = an
case *Number:
ab.Number = an
case *UnquotedString:
ab.UnquotedString = an
case *DoubleQuotedString:
ab.DoubleQuotedString = an
case *SingleQuotedString:
ab.SingleQuotedString = an
case *BlockString:
ab.BlockString = an
case *Array:
ab.Array = an
case *Map:
ab.Map = an
}
return ab
}
func (ab ArrayNodeBox) Unbox() ArrayNode {
switch {
case ab.Comment != nil:
return ab.Comment
case ab.BlockComment != nil:
return ab.BlockComment
case ab.Substitution != nil:
return ab.Substitution
case ab.Import != nil:
return ab.Import
case ab.Null != nil:
return ab.Null
case ab.Boolean != nil:
return ab.Boolean
case ab.Number != nil:
return ab.Number
case ab.UnquotedString != nil:
return ab.UnquotedString
case ab.DoubleQuotedString != nil:
return ab.DoubleQuotedString
case ab.SingleQuotedString != nil:
return ab.SingleQuotedString
case ab.BlockString != nil:
return ab.BlockString
case ab.Array != nil:
return ab.Array
case ab.Map != nil:
return ab.Map
default:
return nil
}
}
// ValueBox is used to box Value for JSON persistence.
type ValueBox struct {
Null *Null `json:"null,omitempty"`
Suspension *Suspension `json:"suspension,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 (vb ValueBox) Unbox() Value {
switch {
case vb.Null != nil:
return vb.Null
case vb.Suspension != nil:
return vb.Suspension
case vb.Boolean != nil:
return vb.Boolean
case vb.Number != nil:
return vb.Number
case vb.UnquotedString != nil:
return vb.UnquotedString
case vb.DoubleQuotedString != nil:
return vb.DoubleQuotedString
case vb.SingleQuotedString != nil:
return vb.SingleQuotedString
case vb.BlockString != nil:
return vb.BlockString
case vb.Array != nil:
return vb.Array
case vb.Map != nil:
return vb.Map
case vb.Import != nil:
return vb.Import
default:
return nil
}
}
func MakeValueBox(v Value) ValueBox {
var vb ValueBox
switch v := v.(type) {
case *Null:
vb.Null = v
case *Suspension:
vb.Suspension = v
case *Boolean:
vb.Boolean = v
case *Number:
vb.Number = v
case *UnquotedString:
vb.UnquotedString = v
case *DoubleQuotedString:
vb.DoubleQuotedString = v
case *SingleQuotedString:
vb.SingleQuotedString = v
case *BlockString:
vb.BlockString = v
case *Array:
vb.Array = v
case *Map:
vb.Map = v
case *Import:
vb.Import = v
}
return vb
}
func (vb ValueBox) ScalarBox() ScalarBox {
var sb ScalarBox
sb.Null = vb.Null
sb.Suspension = vb.Suspension
sb.Boolean = vb.Boolean
sb.Number = vb.Number
sb.UnquotedString = vb.UnquotedString
sb.DoubleQuotedString = vb.DoubleQuotedString
sb.SingleQuotedString = vb.SingleQuotedString
sb.BlockString = vb.BlockString
return sb
}
func (vb ValueBox) StringBox() *StringBox {
var sb StringBox
sb.UnquotedString = vb.UnquotedString
sb.DoubleQuotedString = vb.DoubleQuotedString
sb.SingleQuotedString = vb.SingleQuotedString
sb.BlockString = vb.BlockString
return &sb
}
// ScalarBox is used to box Scalar for JSON persistence.
// TODO: implement ScalarString()
type ScalarBox struct {
Null *Null `json:"null,omitempty"`
Suspension *Suspension `json:"suspension,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 (sb ScalarBox) Unbox() Scalar {
switch {
case sb.Null != nil:
return sb.Null
case sb.Suspension != nil:
return sb.Suspension
case sb.Boolean != nil:
return sb.Boolean
case sb.Number != nil:
return sb.Number
case sb.UnquotedString != nil:
return sb.UnquotedString
case sb.DoubleQuotedString != nil:
return sb.DoubleQuotedString
case sb.SingleQuotedString != nil:
return sb.SingleQuotedString
case sb.BlockString != nil:
return sb.BlockString
default:
return nil
}
}
func (sb ScalarBox) ScalarString() string {
return sb.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 (sb *StringBox) Unbox() String {
switch {
case sb.UnquotedString != nil:
return sb.UnquotedString
case sb.DoubleQuotedString != nil:
return sb.DoubleQuotedString
case sb.SingleQuotedString != nil:
return sb.SingleQuotedString
case sb.BlockString != nil:
return sb.BlockString
default:
return nil
}
}
func (sb *StringBox) ScalarString() string {
return sb.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 RawString(s string, inKey bool) String {
if s == "" {
return FlatDoubleQuotedString(s)
}
if inKey {
for i, r := range s {
switch r {
case '-':
if i+1 < len(s) && s[i+1] != '-' {
continue
}
}
if strings.ContainsRune(UnquotedKeySpecials, r) {
if !strings.ContainsRune(s, '"') {
return FlatDoubleQuotedString(s)
}
if strings.ContainsRune(s, '\n') {
return FlatDoubleQuotedString(s)
}
return &SingleQuotedString{Value: s}
}
}
} else if s == "null" || s == "suspend" || s == "unsuspend" || strings.ContainsAny(s, UnquotedValueSpecials) {
if !strings.ContainsRune(s, '"') && !strings.ContainsRune(s, '$') {
return FlatDoubleQuotedString(s)
}
if strings.ContainsRune(s, '\n') {
return FlatDoubleQuotedString(s)
}
return &SingleQuotedString{Value: s}
}
if hasSurroundingWhitespace(s) {
return FlatDoubleQuotedString(s)
}
return FlatUnquotedString(s)
}
func RawStringBox(s string, inKey bool) *StringBox {
return MakeValueBox(RawString(s, inKey)).StringBox()
}
func hasSurroundingWhitespace(s string) bool {
r, _ := utf8.DecodeRuneInString(s)
r2, _ := utf8.DecodeLastRuneInString(s)
return unicode.IsSpace(r) || unicode.IsSpace(r2)
}
func (s *Substitution) IDA() (ida []string) {
for _, el := range s.Path {
ida = append(ida, el.Unbox().ScalarString())
}
return ida
}
func (i *Import) IDA() (ida []String) {
for _, el := range i.Path[1:] {
ida = append(ida, el.Unbox())
}
return ida
}
func (i *Import) PathWithPre() string {
if len(i.Path) == 0 {
return ""
}
return path.Join(i.Pre, i.Path[0].Unbox().ScalarString())
}
func (i *Import) Dir() string {
return path.Dir(i.PathWithPre())
}