text: package parser
  
  import "github.com/hashicorp/hcl/hcl/ast"
  
  // flattenObjects takes an AST node, walks it, and flattens
  func flattenObjects(node ast.Node) {
  	ast.Walk(node, func(n ast.Node) (ast.Node, bool) {
  		// We only care about lists, because this is what we modify
  		list, ok := n.(*ast.ObjectList)
  		if !ok {
  			return n, true
  		}
  
  		// Rebuild the item list
  		items := make([]*ast.ObjectItem, 0, len(list.Items))
  		frontier := make([]*ast.ObjectItem, len(list.Items))
  		copy(frontier, list.Items)
  		for len(frontier) > 0 {
  			// Pop the current item
  			n := len(frontier)
  			item := frontier[n-1]
  			frontier = frontier[:n-1]
  
  			switch v := item.Val.(type) {
  			case *ast.ObjectType:
  				items, frontier = flattenObjectType(v, item, items, frontier)
  			case *ast.ListType:
  				items, frontier = flattenListType(v, item, items, frontier)
  			default:
  				items = append(items, item)
  			}
  		}
  
  		// Reverse the list since the frontier model runs things backwards
  		for i := len(items)/2 - 1; i >= 0; i-- {
  			opp := len(items) - 1 - i
  			items[i], items[opp] = items[opp], items[i]
  		}
  
  		// Done! Set the original items
  		list.Items = items
  		return n, true
  	})
  }
  
  func flattenListType(
  	ot *ast.ListType,
  	item *ast.ObjectItem,
  	items []*ast.ObjectItem,
  	frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) {
  	// If the list is empty, keep the original list
  	if len(ot.List) == 0 {
  		items = append(items, item)
  		return items, frontier
  	}
  
  	// All the elements of this object must also be objects!
  	for _, subitem := range ot.List {
  		if _, ok := subitem.(*ast.ObjectType); !ok {
  			items = append(items, item)
  			return items, frontier
  		}
  	}
  
  	// Great! We have a match go through all the items and flatten
  	for _, elem := range ot.List {
  		// Add it to the frontier so that we can recurse
  		frontier = append(frontier, &ast.ObjectItem{
  			Keys:        item.Keys,
  			Assign:      item.Assign,
  			Val:         elem,
  			LeadComment: item.LeadComment,
  			LineComment: item.LineComment,
  		})
  	}
  
  	return items, frontier
  }
  
  func flattenObjectType(
  	ot *ast.ObjectType,
  	item *ast.ObjectItem,
  	items []*ast.ObjectItem,
  	frontier []*ast.ObjectItem) ([]*ast.ObjectItem, []*ast.ObjectItem) {
  	// If the list has no items we do not have to flatten anything
  	if ot.List.Items == nil {
  		items = append(items, item)
  		return items, frontier
  	}
  
  	// All the elements of this object must also be objects!
  	for _, subitem := range ot.List.Items {
  		if _, ok := subitem.Val.(*ast.ObjectType); !ok {
  			items = append(items, item)
  			return items, frontier
  		}
  	}
  
  	// Great! We have a match go through all the items and flatten
  	for _, subitem := range ot.List.Items {
  		// Copy the new key
  		keys := make([]*ast.ObjectKey, len(item.Keys)+len(subitem.Keys))
  		copy(keys, item.Keys)
  		copy(keys[len(item.Keys):], subitem.Keys)
  
  		// Add it to the frontier so that we can recurse
  		frontier = append(frontier, &ast.ObjectItem{
  			Keys:        keys,
  			Assign:      item.Assign,
  			Val:         subitem.Val,
  			LeadComment: item.LeadComment,
  			LineComment: item.LineComment,
  		})
  	}
  
  	return items, frontier
  }
  