internal/parser/hcl_node.go (394 lines of code) (raw):
package parser
import (
"fmt"
"log"
"strings"
"github.com/Azure/azapi-lsp/internal/utils"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
)
type KeyValueFormat int
const (
KeyEqualValue KeyValueFormat = iota
QuotedKeyEqualValue
QuotedKeyColonValue
)
type HclNode struct {
Children map[string]*HclNode
KeyRange, ValueRange, EqualRange hcl.Range
Value *string
Key string
KeyValueFormat KeyValueFormat
}
func (rm HclNode) GetRange() hcl.Range {
return RangeOver(RangeOver(rm.KeyRange, rm.ValueRange), rm.EqualRange)
}
func (rm HclNode) IsValueArray() bool {
if rm.Value != nil {
return false
}
prefix := rm.Key + "."
for _, child := range rm.Children {
if !strings.HasPrefix(child.Key, prefix) {
return false
}
}
return true
}
func (rm HclNode) IsValueMap() bool {
if rm.Value != nil {
return false
}
prefix := rm.Key + "."
for _, child := range rm.Children {
if strings.HasPrefix(child.Key, prefix) {
return false
}
}
return true
}
func HclNodeArraysOfPos(hclNode *HclNode, pos hcl.Pos) []*HclNode {
if hclNode == nil {
return nil
}
if ContainsPos(hclNode.GetRange(), pos) {
res := make([]*HclNode, 0)
if hclNode.Key != "" {
res = append(res, hclNode)
}
for _, child := range hclNode.Children {
if arr := HclNodeArraysOfPos(child, pos); len(arr) != 0 {
return append(res, arr...)
}
}
return res
}
return nil
}
type State struct {
CurrentHclNode *HclNode
Key *string
Value *string
KeyRange, ValueRange, EqualRange hcl.Range
Index *int
ExpectKey bool
ExpectEqual bool
}
func (s State) GetCurrentKey() string {
key := "key_placeholder"
if s.Key != nil {
key = *s.Key
}
if s.Index != nil {
return fmt.Sprintf("%s.%d", key, *s.Index)
}
return key
}
func BuildHclNode(tokens hclsyntax.Tokens) *HclNode {
stack := make([]State, 0)
stack = append(stack, State{
CurrentHclNode: &HclNode{
KeyValueFormat: KeyEqualValue,
Children: make(map[string]*HclNode),
},
Key: utils.String("dummy"),
Index: nil,
Value: nil,
ExpectKey: false,
ExpectEqual: false,
})
for _, token := range tokens {
switch token.Type {
case hclsyntax.TokenOBrace: // {
top := len(stack) - 1
if top < 0 {
return nil
}
state := stack[top]
key := state.GetCurrentKey()
if state.ExpectKey {
log.Printf("[WARN] expect key but got {")
}
hclNode := &HclNode{
Key: key,
KeyRange: state.KeyRange,
ValueRange: token.Range,
EqualRange: state.EqualRange,
Children: make(map[string]*HclNode),
KeyValueFormat: stack[top].CurrentHclNode.KeyValueFormat,
}
stack[top].CurrentHclNode.Children[key] = hclNode
if stack[top].Index == nil {
stack[top].ExpectKey = true
stack[top].ExpectEqual = true
} else {
// if there's an index, then expect value because p = [{}]
stack[top].ExpectKey = false
stack[top].ExpectEqual = false
// the hclNode is an object in an array, so use the range of "{" as the key range
hclNode.KeyRange = token.Range
}
stack = append(stack, State{
ExpectKey: true,
ExpectEqual: true,
CurrentHclNode: hclNode,
})
case hclsyntax.TokenCBrace: // }
top := len(stack) - 1
if top < 0 {
return nil
}
state := stack[top]
if !state.ExpectKey {
log.Printf("[WARN] expect value but got }")
key := state.GetCurrentKey()
stack[top].CurrentHclNode.Children[key] = &HclNode{
Key: key,
Value: state.Value,
KeyRange: state.KeyRange,
ValueRange: state.ValueRange,
EqualRange: state.EqualRange,
}
}
stack[top].CurrentHclNode.ValueRange = RangeOver(stack[top].CurrentHclNode.ValueRange, token.Range)
stack = stack[0:top]
case hclsyntax.TokenOBrack: // [
top := len(stack) - 1
if top < 0 {
return nil
}
state := stack[top]
if state.ExpectKey {
log.Printf("[WARN] expect key but got [")
}
if state.Value != nil && *state.Value != "" {
updateStateValue(&stack[top], token)
break
}
key := state.GetCurrentKey()
hclNode := &HclNode{
Key: key,
KeyRange: state.KeyRange,
EqualRange: state.EqualRange,
ValueRange: token.Range,
KeyValueFormat: state.CurrentHclNode.KeyValueFormat,
Children: make(map[string]*HclNode),
}
stack[top].CurrentHclNode.Children[key] = hclNode
stack[top].ExpectKey = true
stack[top].ExpectEqual = stack[top].ExpectKey
stack = append(stack, State{
ExpectKey: false,
ExpectEqual: false,
Key: state.Key,
Index: utils.Int(0),
CurrentHclNode: hclNode,
})
case hclsyntax.TokenCBrack: // ]
top := len(stack) - 1
if top < 0 {
return nil
}
state := stack[top]
if state.Value != nil && *state.Value != "" && strings.Contains(*state.Value, "[") {
updateStateValue(&stack[top], token)
break
}
if _, ok := state.CurrentHclNode.Children[state.GetCurrentKey()]; !ok {
log.Printf("[WARN] expect value but got }")
// avoid adding an empty element
if !state.ValueRange.Empty() {
key := state.GetCurrentKey()
stack[top].CurrentHclNode.Children[key] = &HclNode{
Key: key,
Value: state.Value,
ValueRange: state.ValueRange,
}
}
}
stack[top].CurrentHclNode.ValueRange = RangeOver(stack[top].CurrentHclNode.ValueRange, token.Range)
stack = stack[0:top]
case hclsyntax.TokenQuotedLit:
top := len(stack) - 1
if top < 0 {
return nil
}
if stack[top].ExpectKey {
foundKey(&stack[top], token)
stack[top].CurrentHclNode.KeyValueFormat = QuotedKeyEqualValue
} else {
updateStateValue(&stack[top], token)
}
case hclsyntax.TokenIdent:
top := len(stack) - 1
if top < 0 {
return nil
}
if stack[top].ExpectKey {
foundKey(&stack[top], token)
} else {
updateStateValue(&stack[top], token)
}
case hclsyntax.TokenColon: // :
top := len(stack) - 1
if top < 0 {
return nil
}
if stack[top].ExpectEqual {
if stack[top].ExpectKey {
log.Printf("[WARN] expect key but got =")
stack[top].ExpectKey = false
}
stack[top].CurrentHclNode.KeyValueFormat = QuotedKeyColonValue
stack[top].EqualRange = token.Range
stack[top].ExpectEqual = false
} else {
updateStateValue(&stack[top], token)
}
case hclsyntax.TokenEqual: // =
top := len(stack) - 1
if top < 0 {
return nil
}
if stack[top].ExpectEqual {
if stack[top].ExpectKey {
log.Printf("[WARN] expect key but got =")
stack[top].ExpectKey = false
}
stack[top].EqualRange = token.Range
stack[top].ExpectEqual = false
} else {
updateStateValue(&stack[top], token)
}
case hclsyntax.TokenNewline:
top := len(stack) - 1
if top < 0 {
return nil
}
state := stack[top]
if !state.ExpectKey {
if stack[top].Index == nil {
key := state.GetCurrentKey()
stack[top].CurrentHclNode.Children[key] = &HclNode{
Key: key,
Value: state.Value,
KeyRange: state.KeyRange,
ValueRange: state.ValueRange,
EqualRange: state.EqualRange,
}
stack[top].Key = nil
stack[top].Value = nil
stack[top].ExpectKey = true
stack[top].ExpectEqual = stack[top].ExpectKey
}
}
case hclsyntax.TokenComma:
top := len(stack) - 1
if top < 0 {
return nil
}
state := stack[top]
if !state.ExpectKey {
if state.Index == nil {
log.Printf("[WARN] unexpected symbol: ,")
break
}
if state.Value != nil {
key := state.GetCurrentKey()
stack[top].CurrentHclNode.Children[key] = &HclNode{
Key: key,
Value: state.Value,
ValueRange: state.ValueRange,
}
}
*stack[top].Index++
stack[top].Value = nil
stack[top].ValueRange = hcl.Range{}
} else {
log.Printf("[WARN] expect key but got ,")
}
case hclsyntax.TokenComment:
comment := string(token.Bytes)
if strings.HasSuffix(comment, "\n") {
top := len(stack) - 1
if top < 0 {
return nil
}
state := stack[top]
if !state.ExpectKey {
if stack[top].Index == nil {
key := state.GetCurrentKey()
stack[top].CurrentHclNode.Children[key] = &HclNode{
Key: key,
Value: state.Value,
KeyRange: state.KeyRange,
ValueRange: state.ValueRange,
EqualRange: state.EqualRange,
}
stack[top].Key = nil
stack[top].Value = nil
stack[top].ExpectKey = true
stack[top].ExpectEqual = stack[top].ExpectKey
}
}
}
default:
top := len(stack) - 1
if top < 0 {
return nil
}
if !stack[top].ExpectEqual {
updateStateValue(&stack[top], token)
}
}
}
if len(stack) == 0 {
return nil
}
root := stack[0].CurrentHclNode
updateValueRange(root)
fixEmptyValueRange(root)
return root
}
func foundKey(st *State, token hclsyntax.Token) {
st.Key = utils.String(string(token.Bytes))
st.KeyRange = token.Range
st.Index = nil
st.Value = nil
st.ValueRange = hcl.Range{}
st.EqualRange = hcl.Range{}
st.ExpectKey = false
}
func updateStateValue(st *State, token hclsyntax.Token) {
if st.Value == nil {
st.Value = utils.String(string(token.Bytes))
} else {
*st.Value = *st.Value + string(token.Bytes)
}
st.ValueRange = RangeOver(st.ValueRange, token.Range)
}
func updateValueRange(hclNode *HclNode) {
if hclNode == nil {
return
}
for _, v := range hclNode.Children {
updateValueRange(v)
hclNode.ValueRange = RangeOver(hclNode.ValueRange, v.GetRange())
}
}
func fixEmptyValueRange(hclNode *HclNode) {
if hclNode == nil {
return
}
if hclNode.Children == nil {
if !hclNode.KeyRange.Empty() && !hclNode.EqualRange.Empty() && hclNode.ValueRange.Empty() {
line := hclNode.EqualRange.End.Line
column := hclNode.EqualRange.End.Column
hclNode.ValueRange = hcl.Range{
Start: hcl.Pos{Line: line, Column: column + 1, Byte: hclNode.EqualRange.End.Byte},
End: hcl.Pos{Line: line + 1, Byte: hclNode.EqualRange.End.Byte + 1},
}
}
} else {
for _, child := range hclNode.Children {
fixEmptyValueRange(child)
}
}
}