github.com/hashicorp/hcl/v2@v2.20.0/hclwrite/node.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package hclwrite 5 6 import ( 7 "fmt" 8 9 "github.com/google/go-cmp/cmp" 10 ) 11 12 // node represents a node in the AST. 13 type node struct { 14 content nodeContent 15 16 list *nodes 17 before, after *node 18 } 19 20 func newNode(c nodeContent) *node { 21 return &node{ 22 content: c, 23 } 24 } 25 26 func (n *node) Equal(other *node) bool { 27 return cmp.Equal(n.content, other.content) 28 } 29 30 func (n *node) BuildTokens(to Tokens) Tokens { 31 return n.content.BuildTokens(to) 32 } 33 34 // Detach removes the receiver from the list it currently belongs to. If the 35 // node is not currently in a list, this is a no-op. 36 func (n *node) Detach() { 37 if n.list == nil { 38 return 39 } 40 if n.before != nil { 41 n.before.after = n.after 42 } 43 if n.after != nil { 44 n.after.before = n.before 45 } 46 if n.list.first == n { 47 n.list.first = n.after 48 } 49 if n.list.last == n { 50 n.list.last = n.before 51 } 52 n.list = nil 53 n.before = nil 54 n.after = nil 55 } 56 57 // ReplaceWith removes the receiver from the list it currently belongs to and 58 // inserts a new node with the given content in its place. If the node is not 59 // currently in a list, this function will panic. 60 // 61 // The return value is the newly-constructed node, containing the given content. 62 // After this function returns, the reciever is no longer attached to a list. 63 func (n *node) ReplaceWith(c nodeContent) *node { 64 if n.list == nil { 65 panic("can't replace node that is not in a list") 66 } 67 68 before := n.before 69 after := n.after 70 list := n.list 71 n.before, n.after, n.list = nil, nil, nil 72 73 nn := newNode(c) 74 nn.before = before 75 nn.after = after 76 nn.list = list 77 if before != nil { 78 before.after = nn 79 } 80 if after != nil { 81 after.before = nn 82 } 83 return nn 84 } 85 86 func (n *node) assertUnattached() { 87 if n.list != nil { 88 panic(fmt.Sprintf("attempt to attach already-attached node %#v", n)) 89 } 90 } 91 92 // nodeContent is the interface type implemented by all AST content types. 93 type nodeContent interface { 94 walkChildNodes(w internalWalkFunc) 95 BuildTokens(to Tokens) Tokens 96 } 97 98 // nodes is a list of nodes. 99 type nodes struct { 100 first, last *node 101 } 102 103 func (ns *nodes) BuildTokens(to Tokens) Tokens { 104 for n := ns.first; n != nil; n = n.after { 105 to = n.BuildTokens(to) 106 } 107 return to 108 } 109 110 func (ns *nodes) Clear() { 111 ns.first = nil 112 ns.last = nil 113 } 114 115 func (ns *nodes) Append(c nodeContent) *node { 116 n := &node{ 117 content: c, 118 } 119 ns.AppendNode(n) 120 n.list = ns 121 return n 122 } 123 124 func (ns *nodes) AppendNode(n *node) { 125 if ns.last != nil { 126 n.before = ns.last 127 ns.last.after = n 128 } 129 n.list = ns 130 ns.last = n 131 if ns.first == nil { 132 ns.first = n 133 } 134 } 135 136 // Insert inserts a nodeContent at a given position. 137 // This is just a wrapper for InsertNode. See InsertNode for details. 138 func (ns *nodes) Insert(pos *node, c nodeContent) *node { 139 n := &node{ 140 content: c, 141 } 142 ns.InsertNode(pos, n) 143 n.list = ns 144 return n 145 } 146 147 // InsertNode inserts a node at a given position. 148 // The first argument is a node reference before which to insert. 149 // To insert it to an empty list, set position to nil. 150 func (ns *nodes) InsertNode(pos *node, n *node) { 151 if pos == nil { 152 // inserts n to empty list. 153 ns.first = n 154 ns.last = n 155 } else { 156 // inserts n before pos. 157 pos.before.after = n 158 n.before = pos.before 159 pos.before = n 160 n.after = pos 161 } 162 163 n.list = ns 164 } 165 166 func (ns *nodes) AppendUnstructuredTokens(tokens Tokens) *node { 167 if len(tokens) == 0 { 168 return nil 169 } 170 n := newNode(tokens) 171 ns.AppendNode(n) 172 n.list = ns 173 return n 174 } 175 176 // FindNodeWithContent searches the nodes for a node whose content equals 177 // the given content. If it finds one then it returns it. Otherwise it returns 178 // nil. 179 func (ns *nodes) FindNodeWithContent(content nodeContent) *node { 180 for n := ns.first; n != nil; n = n.after { 181 if n.content == content { 182 return n 183 } 184 } 185 return nil 186 } 187 188 // nodeSet is an unordered set of nodes. It is used to describe a set of nodes 189 // that all belong to the same list that have some role or characteristic 190 // in common. 191 type nodeSet map[*node]struct{} 192 193 func newNodeSet() nodeSet { 194 return make(nodeSet) 195 } 196 197 func (ns nodeSet) Has(n *node) bool { 198 if ns == nil { 199 return false 200 } 201 _, exists := ns[n] 202 return exists 203 } 204 205 func (ns nodeSet) Add(n *node) { 206 ns[n] = struct{}{} 207 } 208 209 func (ns nodeSet) Remove(n *node) { 210 delete(ns, n) 211 } 212 213 func (ns nodeSet) Clear() { 214 for n := range ns { 215 delete(ns, n) 216 } 217 } 218 219 func (ns nodeSet) List() []*node { 220 if len(ns) == 0 { 221 return nil 222 } 223 224 ret := make([]*node, 0, len(ns)) 225 226 // Determine which list we are working with. We assume here that all of 227 // the nodes belong to the same list, since that is part of the contract 228 // for nodeSet. 229 var list *nodes 230 for n := range ns { 231 list = n.list 232 break 233 } 234 235 // We recover the order by iterating over the whole list. This is not 236 // the most efficient way to do it, but our node lists should always be 237 // small so not worth making things more complex. 238 for n := list.first; n != nil; n = n.after { 239 if ns.Has(n) { 240 ret = append(ret, n) 241 } 242 } 243 return ret 244 } 245 246 // FindNodeWithContent searches the nodes for a node whose content equals 247 // the given content. If it finds one then it returns it. Otherwise it returns 248 // nil. 249 func (ns nodeSet) FindNodeWithContent(content nodeContent) *node { 250 for n := range ns { 251 if n.content == content { 252 return n 253 } 254 } 255 return nil 256 } 257 258 type internalWalkFunc func(*node) 259 260 // inTree can be embedded into a content struct that has child nodes to get 261 // a standard implementation of the NodeContent interface and a record of 262 // a potential parent node. 263 type inTree struct { 264 parent *node 265 children *nodes 266 } 267 268 func newInTree() inTree { 269 return inTree{ 270 children: &nodes{}, 271 } 272 } 273 274 func (it *inTree) assertUnattached() { 275 if it.parent != nil { 276 panic(fmt.Sprintf("node is already attached to %T", it.parent.content)) 277 } 278 } 279 280 func (it *inTree) walkChildNodes(w internalWalkFunc) { 281 for n := it.children.first; n != nil; n = n.after { 282 w(n) 283 } 284 } 285 286 func (it *inTree) BuildTokens(to Tokens) Tokens { 287 for n := it.children.first; n != nil; n = n.after { 288 to = n.BuildTokens(to) 289 } 290 return to 291 } 292 293 // leafNode can be embedded into a content struct to give it a do-nothing 294 // implementation of walkChildNodes 295 type leafNode struct { 296 } 297 298 func (n *leafNode) walkChildNodes(w internalWalkFunc) { 299 }