package hclwrite import ( "fmt" "github.com/google/go-cmp/cmp" ) // node represents a node in the AST. type node struct { content nodeContent list *nodes before, after *node } func newNode(c nodeContent) *node { return &node{ content: c, } } func (n *node) Equal(other *node) bool { return cmp.Equal(n.content, other.content) } func (n *node) BuildTokens(to Tokens) Tokens { return n.content.BuildTokens(to) } // Detach removes the receiver from the list it currently belongs to. If the // node is not currently in a list, this is a no-op. func (n *node) Detach() { if n.list == nil { return } if n.before != nil { n.before.after = n.after } if n.after != nil { n.after.before = n.before } if n.list.first == n { n.list.first = n.after } if n.list.last == n { n.list.last = n.before } n.list = nil n.before = nil n.after = nil } // ReplaceWith removes the receiver from the list it currently belongs to and // inserts a new node with the given content in its place. If the node is not // currently in a list, this function will panic. // // The return value is the newly-constructed node, containing the given content. // After this function returns, the reciever is no longer attached to a list. func (n *node) ReplaceWith(c nodeContent) *node { if n.list == nil { panic("can't replace node that is not in a list") } before := n.before after := n.after list := n.list n.before, n.after, n.list = nil, nil, nil nn := newNode(c) nn.before = before nn.after = after nn.list = list if before != nil { before.after = nn } if after != nil { after.before = nn } return nn } func (n *node) assertUnattached() { if n.list != nil { panic(fmt.Sprintf("attempt to attach already-attached node %#v", n)) } } // nodeContent is the interface type implemented by all AST content types. type nodeContent interface { walkChildNodes(w internalWalkFunc) BuildTokens(to Tokens) Tokens } // nodes is a list of nodes. type nodes struct { first, last *node } func (ns *nodes) BuildTokens(to Tokens) Tokens { for n := ns.first; n != nil; n = n.after { to = n.BuildTokens(to) } return to } func (ns *nodes) Clear() { ns.first = nil ns.last = nil } func (ns *nodes) Append(c nodeContent) *node { n := &node{ content: c, } ns.AppendNode(n) n.list = ns return n } func (ns *nodes) AppendNode(n *node) { if ns.last != nil { n.before = ns.last ns.last.after = n } n.list = ns ns.last = n if ns.first == nil { ns.first = n } } func (ns *nodes) AppendUnstructuredTokens(tokens Tokens) *node { if len(tokens) == 0 { return nil } n := newNode(tokens) ns.AppendNode(n) n.list = ns return n } // nodeSet is an unordered set of nodes. It is used to describe a set of nodes // that all belong to the same list that have some role or characteristic // in common. type nodeSet map[*node]struct{} func newNodeSet() nodeSet { return make(nodeSet) } func (ns nodeSet) Has(n *node) bool { if ns == nil { return false } _, exists := ns[n] return exists } func (ns nodeSet) Add(n *node) { ns[n] = struct{}{} } func (ns nodeSet) Remove(n *node) { delete(ns, n) } func (ns nodeSet) List() []*node { if len(ns) == 0 { return nil } ret := make([]*node, 0, len(ns)) // Determine which list we are working with. We assume here that all of // the nodes belong to the same list, since that is part of the contract // for nodeSet. var list *nodes for n := range ns { list = n.list break } // We recover the order by iterating over the whole list. This is not // the most efficient way to do it, but our node lists should always be // small so not worth making things more complex. for n := list.first; n != nil; n = n.after { if ns.Has(n) { ret = append(ret, n) } } return ret } type internalWalkFunc func(*node) // inTree can be embedded into a content struct that has child nodes to get // a standard implementation of the NodeContent interface and a record of // a potential parent node. type inTree struct { parent *node children *nodes } func newInTree() inTree { return inTree{ children: &nodes{}, } } func (it *inTree) assertUnattached() { if it.parent != nil { panic(fmt.Sprintf("node is already attached to %T", it.parent.content)) } } func (it *inTree) walkChildNodes(w internalWalkFunc) { for n := it.children.first; n != nil; n = n.after { w(n) } } func (it *inTree) BuildTokens(to Tokens) Tokens { for n := it.children.first; n != nil; n = n.after { to = n.BuildTokens(to) } return to } // leafNode can be embedded into a content struct to give it a do-nothing // implementation of walkChildNodes type leafNode struct { } func (n *leafNode) walkChildNodes(w internalWalkFunc) { }