8 "github.com/hashicorp/hcl2/hcl"
9 "github.com/hashicorp/hcl2/hcl/hclsyntax"
10 "github.com/zclconf/go-cty/cty"
13 // TokensForValue returns a sequence of tokens that represents the given
16 // This function only supports types that are used by HCL. In particular, it
17 // does not support capsule types and will panic if given one.
19 // It is not possible to express an unknown value in source code, so this
20 // function will panic if the given value is unknown or contains any unknown
21 // values. A caller can call the value's IsWhollyKnown method to verify that
22 // no unknown values are present before calling TokensForValue.
23 func TokensForValue(val cty.Value) Tokens {
24 toks := appendTokensForValue(val, nil)
25 format(toks) // fiddle with the SpacesBefore field to get canonical spacing
29 // TokensForTraversal returns a sequence of tokens that represents the given
32 // If the traversal is absolute then the result is a self-contained, valid
33 // reference expression. If the traversal is relative then the returned tokens
34 // could be appended to some other expression tokens to traverse into the
35 // represented expression.
36 func TokensForTraversal(traversal hcl.Traversal) Tokens {
37 toks := appendTokensForTraversal(traversal, nil)
38 format(toks) // fiddle with the SpacesBefore field to get canonical spacing
42 func appendTokensForValue(val cty.Value, toks Tokens) Tokens {
46 panic("cannot produce tokens for unknown value")
49 toks = append(toks, &Token{
50 Type: hclsyntax.TokenIdent,
51 Bytes: []byte(`null`),
54 case val.Type() == cty.Bool:
61 toks = append(toks, &Token{
62 Type: hclsyntax.TokenIdent,
66 case val.Type() == cty.Number:
67 bf := val.AsBigFloat()
68 srcStr := bf.Text('f', -1)
69 toks = append(toks, &Token{
70 Type: hclsyntax.TokenNumberLit,
71 Bytes: []byte(srcStr),
74 case val.Type() == cty.String:
75 // TODO: If it's a multi-line string ending in a newline, format
76 // it as a HEREDOC instead.
77 src := escapeQuotedStringLit(val.AsString())
78 toks = append(toks, &Token{
79 Type: hclsyntax.TokenOQuote,
83 toks = append(toks, &Token{
84 Type: hclsyntax.TokenQuotedLit,
88 toks = append(toks, &Token{
89 Type: hclsyntax.TokenCQuote,
93 case val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType():
94 toks = append(toks, &Token{
95 Type: hclsyntax.TokenOBrack,
100 for it := val.ElementIterator(); it.Next(); {
102 toks = append(toks, &Token{
103 Type: hclsyntax.TokenComma,
107 _, eVal := it.Element()
108 toks = appendTokensForValue(eVal, toks)
112 toks = append(toks, &Token{
113 Type: hclsyntax.TokenCBrack,
117 case val.Type().IsMapType() || val.Type().IsObjectType():
118 toks = append(toks, &Token{
119 Type: hclsyntax.TokenOBrace,
124 for it := val.ElementIterator(); it.Next(); {
126 toks = append(toks, &Token{
127 Type: hclsyntax.TokenComma,
131 eKey, eVal := it.Element()
132 if hclsyntax.ValidIdentifier(eKey.AsString()) {
133 toks = append(toks, &Token{
134 Type: hclsyntax.TokenIdent,
135 Bytes: []byte(eKey.AsString()),
138 toks = appendTokensForValue(eKey, toks)
140 toks = append(toks, &Token{
141 Type: hclsyntax.TokenEqual,
144 toks = appendTokensForValue(eVal, toks)
148 toks = append(toks, &Token{
149 Type: hclsyntax.TokenCBrace,
154 panic(fmt.Sprintf("cannot produce tokens for %#v", val))
160 func appendTokensForTraversal(traversal hcl.Traversal, toks Tokens) Tokens {
161 for _, step := range traversal {
162 appendTokensForTraversalStep(step, toks)
167 func appendTokensForTraversalStep(step hcl.Traverser, toks Tokens) {
168 switch ts := step.(type) {
169 case hcl.TraverseRoot:
170 toks = append(toks, &Token{
171 Type: hclsyntax.TokenIdent,
172 Bytes: []byte(ts.Name),
174 case hcl.TraverseAttr:
178 Type: hclsyntax.TokenDot,
182 Type: hclsyntax.TokenIdent,
183 Bytes: []byte(ts.Name),
186 case hcl.TraverseIndex:
187 toks = append(toks, &Token{
188 Type: hclsyntax.TokenOBrack,
191 appendTokensForValue(ts.Key, toks)
192 toks = append(toks, &Token{
193 Type: hclsyntax.TokenCBrack,
197 panic(fmt.Sprintf("unsupported traversal step type %T", step))
201 func escapeQuotedStringLit(s string) []byte {
205 buf := make([]byte, 0, len(s))
206 for i, r := range s {
209 buf = append(buf, '\\', 'n')
211 buf = append(buf, '\\', 'r')
213 buf = append(buf, '\\', 't')
215 buf = append(buf, '\\', '"')
217 buf = append(buf, '\\', '\\')
219 buf = appendRune(buf, r)
221 if len(remain) > 0 && remain[0] == '{' {
222 // Double up our template introducer symbol to escape it.
223 buf = appendRune(buf, r)
226 if !unicode.IsPrint(r) {
229 fmted = fmt.Sprintf("\\u%04x", r)
231 fmted = fmt.Sprintf("\\U%08x", r)
233 buf = append(buf, fmted...)
235 buf = appendRune(buf, r)
242 func appendRune(b []byte, r rune) []byte {
244 for i := 0; i < l; i++ {
245 b = append(b, 0) // make room at the end of our buffer
248 utf8.EncodeRune(ch, r)