zcl/hclhil: remove HCL/HIL support

This package was a prototype of wrapping the HCL/HIL API in the new zcl
API as a compatibility strategy. This avenue wasn't chosen in the end, so
we'll remove this to avoid confusion as we rename everything else in this
repository to be called "hcl" now.
This commit is contained in:
Martin Atkins 2017-09-11 15:46:01 -07:00
parent c3ca111fff
commit 386f7134f1
10 changed files with 7 additions and 1859 deletions

View File

@ -1,8 +0,0 @@
// Package hclhil adapts Hashicorp Configuration Language and Hashicorp
// Interpolation Language (HCL and HIL, respectively) to work within the
// ZCL API.
//
// This is intended to help applications that previously used HCL/HIL to
// gradually adopt zcl while remaining generally compatible with their
// previous configuration formats.
package hclhil

View File

@ -1,29 +0,0 @@
package hclhil
import (
"fmt"
"github.com/hashicorp/hcl"
hclast "github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/hcl2/zcl"
)
func parse(src []byte, filename string) (*zcl.File, zcl.Diagnostics) {
hclFile, err := hcl.ParseBytes(src)
if err != nil {
return nil, zcl.Diagnostics{
&zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: "Syntax error in configuration",
Detail: fmt.Sprintf("The file %q could not be parsed: %s", filename, err),
Subject: errorRange(err),
},
}
}
return &zcl.File{
Body: &body{
oli: hclFile.Node.(*hclast.ObjectList),
},
}, nil
}

View File

@ -1,69 +0,0 @@
package hclhil
import (
"fmt"
"io/ioutil"
"os"
"github.com/hashicorp/hcl2/zcl"
)
// Parse attempts to parse the given buffer as HCL with HIL expressions and,
// if successful, returns a zcl.File for the zcl configuration represented by
// it.
//
// The returned file is valid only if the returned diagnostics returns false
// from its HasErrors method. If HasErrors returns true, the file represents
// the subset of data that was able to be parsed, which may be none.
func Parse(src []byte, filename string) (*zcl.File, zcl.Diagnostics) {
return parse(src, filename)
}
// ParseFile is a convenience wrapper around Parse that first attempts to load
// data from the given filename, passing the result to Parse if successful.
//
// If the file cannot be read, an error diagnostic with nil context is returned.
func ParseFile(filename string) (*zcl.File, zcl.Diagnostics) {
f, err := os.Open(filename)
if err != nil {
return nil, zcl.Diagnostics{
{
Severity: zcl.DiagError,
Summary: "Failed to open file",
Detail: fmt.Sprintf("The file %q could not be opened.", filename),
},
}
}
defer f.Close()
src, err := ioutil.ReadAll(f)
if err != nil {
return nil, zcl.Diagnostics{
{
Severity: zcl.DiagError,
Summary: "Failed to read file",
Detail: fmt.Sprintf("The file %q was opened, but an error occured while reading it.", filename),
},
}
}
return Parse(src, filename)
}
// ParseTemplate attempts to parse the given buffer as a HIL template and,
// if successful, returns a zcl.Expression for the value represented by it.
//
// The returned file is valid only if the returned diagnostics returns false
// from its HasErrors method. If HasErrors returns true, the file represents
// the subset of data that was able to be parsed, which may be none.
func ParseTemplate(src []byte, filename string) (zcl.Expression, zcl.Diagnostics) {
return parseTemplate(src, filename, zcl.Pos{Line: 1, Column: 1})
}
// ParseTemplateEmbedded is like ParseTemplate but is for templates that are
// embedded in a file in another language. Practically-speaking this just
// offsets the source positions returned in diagnostics, etc to be relative
// to the given position.
func ParseTemplateEmbedded(src []byte, filename string, startPos zcl.Pos) (zcl.Expression, zcl.Diagnostics) {
return parseTemplate(src, filename, startPos)
}

View File

@ -1,67 +0,0 @@
package hclhil
import (
hclparser "github.com/hashicorp/hcl/hcl/parser"
hcltoken "github.com/hashicorp/hcl/hcl/token"
"github.com/hashicorp/hcl2/zcl"
hilast "github.com/hashicorp/hil/ast"
hilparser "github.com/hashicorp/hil/parser"
)
// errorRange attempts to extract a source range from the given error,
// returning a pointer to the range if possible or nil if not.
//
// errorRange understands HCL's "PosError" type, which wraps an error
// with a source position.
func errorRange(err error) *zcl.Range {
switch terr := err.(type) {
case *hclparser.PosError:
rng := rangeFromHCLPos(terr.Pos)
return &rng
case *hilparser.ParseError:
rng := rangeFromHILPos(terr.Pos)
return &rng
default:
return nil
}
}
func rangeFromHCLPos(pos hcltoken.Pos) zcl.Range {
// HCL only marks single positions rather than ranges, so we adapt this
// by creating a single-character range at the given position.
return zcl.Range{
Filename: pos.Filename,
Start: zcl.Pos{
Byte: pos.Offset,
Line: pos.Line,
Column: pos.Column,
},
End: zcl.Pos{
Byte: pos.Offset + 1,
Line: pos.Line,
Column: pos.Column + 1,
},
}
}
func rangeFromHILPos(pos hilast.Pos) zcl.Range {
// HIL only marks single positions rather than ranges, so we adapt this
// by creating a single-character range at the given position.
// HIL also doesn't track byte offsets, so we will hard-code these to
// zero so that no position can be considered to be "inside" these
// from a byte offset perspective.
return zcl.Range{
Filename: pos.Filename,
Start: zcl.Pos{
Byte: 0,
Line: pos.Line,
Column: pos.Column,
},
End: zcl.Pos{
Byte: 0,
Line: pos.Line,
Column: pos.Column + 1,
},
}
}

View File

@ -1,416 +0,0 @@
package hclhil
import (
"fmt"
"strings"
hclast "github.com/hashicorp/hcl/hcl/ast"
hcltoken "github.com/hashicorp/hcl/hcl/token"
"github.com/hashicorp/hcl2/zcl"
"github.com/zclconf/go-cty/cty"
)
// body is our implementation of zcl.Body in terms of an HCL ObjectList
type body struct {
oli *hclast.ObjectList
hiddenNames map[string]struct{}
}
func (b *body) Content(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Diagnostics) {
content, _, diags := b.content(schema, false)
return content, diags
}
func (b *body) PartialContent(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Body, zcl.Diagnostics) {
return b.content(schema, true)
}
func (b *body) content(schema *zcl.BodySchema, partial bool) (*zcl.BodyContent, zcl.Body, zcl.Diagnostics) {
attrSchemas := make(map[string]zcl.AttributeSchema)
blockSchemas := make(map[string]zcl.BlockHeaderSchema)
for _, attrS := range schema.Attributes {
attrSchemas[attrS.Name] = attrS
}
for _, blockS := range schema.Blocks {
blockSchemas[blockS.Type] = blockS
}
attrs := make(zcl.Attributes)
var blocks zcl.Blocks
var diags zcl.Diagnostics
namesUsed := make(map[string]struct{})
for _, item := range b.oli.Items {
if len(item.Keys) == 0 {
// Should never happen, since we don't use b.oli.Filter
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: "Invalid item",
Detail: "Somehow we have an HCL item with no keys. This should never happen.",
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
})
continue
}
name := item.Keys[0].Token.Value().(string)
if _, hidden := b.hiddenNames[name]; hidden {
continue
}
if _, isAttr := attrSchemas[name]; isAttr {
if len(item.Keys) > 1 {
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: "Unsupported block type",
Detail: fmt.Sprintf("Blocks of type %q are not expected here. Did you mean to define an attribute named %q?", name, name),
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
})
continue
}
diags = append(diags, insertAttr(attrs, item)...)
namesUsed[name] = struct{}{}
} else if blockS, isBlock := blockSchemas[name]; isBlock {
obj, isBlock := item.Val.(*hclast.ObjectType)
if !isBlock {
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: "Unsupported attribute",
Detail: fmt.Sprintf("An attribute named %q is not expected here. Did you mean to define a block of type %q?", name, name),
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
})
continue
}
if item.Assign.Line != 0 {
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagWarning,
Summary: "Attribute syntax used for block",
Detail: fmt.Sprintf("Block %q is defined using attribute syntax, which is deprecated. The equals sign is not used to define a block.", name),
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
})
}
labelKeys := item.Keys[1:]
if len(labelKeys) > len(blockS.LabelNames) {
if len(blockS.LabelNames) == 0 {
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: fmt.Sprintf("Extraneous label for %s", name),
Detail: fmt.Sprintf(
"No labels are expected for %s blocks.", name,
),
Subject: rangeFromHCLPos(labelKeys[len(blockS.LabelNames)].Pos()).Ptr(),
})
} else {
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: fmt.Sprintf("Extraneous label for %s", name),
Detail: fmt.Sprintf(
"Only %d labels (%s) are expected for %s blocks.",
len(blockS.LabelNames), strings.Join(blockS.LabelNames, ", "), name,
),
Subject: rangeFromHCLPos(labelKeys[len(blockS.LabelNames)].Pos()).Ptr(),
})
}
continue
}
if len(labelKeys) < len(blockS.LabelNames) {
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: fmt.Sprintf("Missing %s for %s", blockS.LabelNames[len(labelKeys)], name),
Detail: fmt.Sprintf(
"All %s blocks must have %d labels (%s).",
name, len(blockS.LabelNames), strings.Join(blockS.LabelNames, ", "),
),
Subject: rangeFromHCLPos(obj.Pos()).Ptr(),
})
continue
}
var labels []string
var labelRanges []zcl.Range
if len(labelKeys) > 0 {
labels = make([]string, len(labelKeys))
labelRanges = make([]zcl.Range, len(labelKeys))
for i, objKey := range labelKeys {
labels[i] = objKey.Token.Value().(string)
labelRanges[i] = rangeFromHCLPos(objKey.Pos())
}
}
blocks = append(blocks, &zcl.Block{
Type: name,
Labels: labels,
Body: &body{
oli: obj.List,
},
DefRange: rangeFromHCLPos(obj.Pos()),
TypeRange: rangeFromHCLPos(item.Keys[0].Pos()),
LabelRanges: labelRanges,
})
namesUsed[name] = struct{}{}
} else {
if !partial {
if item.Assign.Line == 0 {
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: "Unsupported block type",
Detail: fmt.Sprintf("Blocks of type %q are not expected here.", name),
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
})
} else {
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: "Unsupported attribute",
Detail: fmt.Sprintf("An attribute named %q is not expected here.", name),
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
})
}
}
}
}
for _, attrS := range schema.Attributes {
if !attrS.Required {
continue
}
if attrs[attrS.Name] == nil {
// HCL has a bug where it panics if you ask for the position of an
// empty object list. This means we can't specify a subject for
// this diagnostic in that case.
var subject *zcl.Range
if len(b.oli.Items) > 0 {
subject = rangeFromHCLPos(b.oli.Pos()).Ptr()
}
diags = diags.Append(&zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: "Missing required attribute",
Detail: fmt.Sprintf("The attribute %q is required, but no definition was found.", attrS.Name),
Subject: subject,
})
}
}
var leftovers zcl.Body
if partial {
for name := range b.hiddenNames {
namesUsed[name] = struct{}{}
}
leftovers = &body{
oli: b.oli,
hiddenNames: namesUsed,
}
}
return &zcl.BodyContent{
Attributes: attrs,
Blocks: blocks,
MissingItemRange: b.MissingItemRange(),
}, leftovers, diags
}
func (b *body) JustAttributes() (zcl.Attributes, zcl.Diagnostics) {
items := b.oli.Items
attrs := make(zcl.Attributes)
var diags zcl.Diagnostics
for _, item := range items {
if len(item.Keys) == 0 {
// Should never happen, since we don't use b.oli.Filter
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: "Invalid item",
Detail: "Somehow we have an HCL item with no keys. This should never happen.",
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
})
continue
}
name := item.Keys[0].Token.Value().(string)
if _, hidden := b.hiddenNames[name]; hidden {
continue
}
if len(item.Keys) > 1 {
name := item.Keys[0].Token.Value().(string)
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: fmt.Sprintf("Unexpected %s block", name),
Detail: "Blocks are not allowed here.",
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
})
continue
}
diags = append(diags, insertAttr(attrs, item)...)
}
return attrs, diags
}
func insertAttr(attrs zcl.Attributes, item *hclast.ObjectItem) zcl.Diagnostics {
name := item.Keys[0].Token.Value().(string)
var diags zcl.Diagnostics
if item.Assign.Line == 0 {
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagWarning,
Summary: "Block syntax used for attribute",
Detail: fmt.Sprintf("Attribute %q is defined using block syntax, which is deprecated. Use an equals sign after the attribute name instead.", name),
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
})
}
if attrs[name] != nil {
diags = append(diags, &zcl.Diagnostic{
Severity: zcl.DiagError,
Summary: "Duplicate attribute definition",
Detail: fmt.Sprintf(
"Attribute %q was previously defined at %s",
name, attrs[name].NameRange.String(),
),
Subject: rangeFromHCLPos(item.Pos()).Ptr(),
})
return diags
}
attrs[name] = &zcl.Attribute{
Name: name,
Expr: &expression{src: item.Val},
Range: rangeFromHCLPos(item.Pos()),
NameRange: rangeFromHCLPos(item.Keys[0].Pos()),
}
return diags
}
func (b *body) MissingItemRange() zcl.Range {
if len(b.oli.Items) == 0 {
// Can't return a sensible range in this case, because HCL panics if
// you ask for the position of an empty list.
return zcl.Range{
Filename: "<unknown>",
}
}
return rangeFromHCLPos(b.oli.Pos())
}
// body is our implementation of zcl.Body in terms of an HCL node, which may
// internally have strings to be interpreted as HIL templates.
type expression struct {
src hclast.Node
}
func (e *expression) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
return ctyValueFromHCLNode(e.src, ctx)
}
func (e *expression) Variables() []zcl.Traversal {
node := e.src
var vars []zcl.Traversal
switch tn := node.(type) {
case *hclast.LiteralType:
tok := tn.Token
switch tok.Type {
case hcltoken.STRING, hcltoken.HEREDOC:
// TODO: HIL parsing and evaluation, if ctx is non-nil.
}
case *hclast.ObjectType:
list := tn.List
attrs, _ := (&body{oli: list}).JustAttributes()
if attrs != nil {
for _, attr := range attrs {
vars = append(vars, attr.Expr.Variables()...)
}
}
case *hclast.ListType:
nodes := tn.List
for _, node := range nodes {
vars = append(vars, (&expression{src: node}).Variables()...)
}
}
return vars
}
func (e *expression) Range() zcl.Range {
return rangeFromHCLPos(e.src.Pos())
}
func (e *expression) StartRange() zcl.Range {
return rangeFromHCLPos(e.src.Pos())
}
func ctyValueFromHCLNode(node hclast.Node, ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
switch tn := node.(type) {
case *hclast.LiteralType:
tok := tn.Token
switch tok.Type {
case hcltoken.NUMBER: // means integer, in HCL land
val := tok.Value().(int64)
return cty.NumberIntVal(val), nil
case hcltoken.FLOAT:
val := tok.Value().(float64)
return cty.NumberFloatVal(val), nil
case hcltoken.STRING, hcltoken.HEREDOC:
val := tok.Value().(string)
// TODO: HIL parsing and evaluation, if ctx is non-nil.
return cty.StringVal(val), nil
case hcltoken.BOOL:
val := tok.Value().(bool)
return cty.BoolVal(val), nil
default:
// should never happen
panic(fmt.Sprintf("unsupported HCL literal type %s", tok.Type))
}
case *hclast.ObjectType:
list := tn.List
attrs, diags := (&body{oli: list}).JustAttributes()
if attrs == nil {
return cty.DynamicVal, diags
}
vals := map[string]cty.Value{}
for name, attr := range attrs {
val, valDiags := attr.Expr.Value(ctx)
if len(valDiags) > 0 {
diags = append(diags, valDiags...)
}
if val == cty.NilVal {
// If we skip one attribute then our return type will be
// inconsistent, so we'll prefer to return dynamic to prevent
// any weird downstream type errors.
return cty.DynamicVal, diags
}
vals[name] = val
}
return cty.ObjectVal(vals), diags
case *hclast.ListType:
nodes := tn.List
vals := make([]cty.Value, len(nodes))
var diags zcl.Diagnostics
for i, node := range nodes {
val, valDiags := ctyValueFromHCLNode(node, ctx)
if len(valDiags) > 0 {
diags = append(diags, valDiags...)
}
if val == cty.NilVal {
// If we skip one element then our return type will be
// inconsistent, so we'll prefer to return dynamic to prevent
// any weird downstream type errors.
return cty.DynamicVal, diags
}
vals[i] = val
}
return cty.TupleVal(vals), diags
default:
panic(fmt.Sprintf("unsupported HCL value type %T", tn))
}
}

View File

@ -1,529 +0,0 @@
package hclhil
import (
"fmt"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
hclast "github.com/hashicorp/hcl/hcl/ast"
hcltoken "github.com/hashicorp/hcl/hcl/token"
"github.com/hashicorp/hcl2/zcl"
"github.com/zclconf/go-cty/cty"
)
func TestBodyPartialContent(t *testing.T) {
tests := []struct {
Source string
Schema *zcl.BodySchema
Want *zcl.BodyContent
DiagCount int
}{
{
``,
&zcl.BodySchema{},
&zcl.BodyContent{
Attributes: zcl.Attributes{},
MissingItemRange: zcl.Range{
Filename: "<unknown>",
},
},
0,
},
{
`foo = 1`,
&zcl.BodySchema{},
&zcl.BodyContent{
Attributes: zcl.Attributes{},
MissingItemRange: zcl.Range{
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
End: zcl.Pos{Line: 1, Column: 2, Byte: 1},
},
},
0,
},
{
`foo = 1`,
&zcl.BodySchema{
Attributes: []zcl.AttributeSchema{
{
Name: "foo",
},
},
},
&zcl.BodyContent{
Attributes: zcl.Attributes{
"foo": {
Name: "foo",
Expr: &expression{
src: &hclast.LiteralType{
Token: hcltoken.Token{
Type: hcltoken.NUMBER,
Text: `1`,
Pos: hcltoken.Pos{
Offset: 6,
Line: 1,
Column: 7,
},
},
},
},
Range: zcl.Range{
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
},
NameRange: zcl.Range{
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
},
},
},
MissingItemRange: zcl.Range{
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
End: zcl.Pos{Line: 1, Column: 2, Byte: 1},
},
},
0,
},
{
``,
&zcl.BodySchema{
Attributes: []zcl.AttributeSchema{
{
Name: "foo",
Required: true,
},
},
},
&zcl.BodyContent{
Attributes: zcl.Attributes{},
MissingItemRange: zcl.Range{
Filename: "<unknown>",
},
},
1, // missing required attribute
},
{
`foo {}`,
&zcl.BodySchema{
Blocks: []zcl.BlockHeaderSchema{
{
Type: "foo",
},
},
},
&zcl.BodyContent{
Attributes: zcl.Attributes{},
Blocks: zcl.Blocks{
{
Type: "foo",
Body: &body{
oli: &hclast.ObjectList{},
},
DefRange: zcl.Range{
Start: zcl.Pos{Byte: 4, Line: 1, Column: 5},
End: zcl.Pos{Byte: 5, Line: 1, Column: 6},
},
TypeRange: zcl.Range{
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
},
},
},
MissingItemRange: zcl.Range{
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
End: zcl.Pos{Line: 1, Column: 2, Byte: 1},
},
},
0,
},
{
`foo "unwanted" {}`,
&zcl.BodySchema{
Blocks: []zcl.BlockHeaderSchema{
{
Type: "foo",
},
},
},
&zcl.BodyContent{
Attributes: zcl.Attributes{},
MissingItemRange: zcl.Range{
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
End: zcl.Pos{Line: 1, Column: 2, Byte: 1},
},
},
1, // no labels are expected
},
{
`foo {}`,
&zcl.BodySchema{
Blocks: []zcl.BlockHeaderSchema{
{
Type: "foo",
LabelNames: []string{"name"},
},
},
},
&zcl.BodyContent{
Attributes: zcl.Attributes{},
MissingItemRange: zcl.Range{
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
End: zcl.Pos{Line: 1, Column: 2, Byte: 1},
},
},
1, // missing name
},
{
`foo "wanted" {}`,
&zcl.BodySchema{
Blocks: []zcl.BlockHeaderSchema{
{
Type: "foo",
LabelNames: []string{"name"},
},
},
},
&zcl.BodyContent{
Attributes: zcl.Attributes{},
Blocks: zcl.Blocks{
{
Type: "foo",
Labels: []string{"wanted"},
Body: &body{
oli: &hclast.ObjectList{},
},
DefRange: zcl.Range{
Start: zcl.Pos{Byte: 13, Line: 1, Column: 14},
End: zcl.Pos{Byte: 14, Line: 1, Column: 15},
},
TypeRange: zcl.Range{
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
},
LabelRanges: []zcl.Range{
{
Start: zcl.Pos{Byte: 4, Line: 1, Column: 5},
End: zcl.Pos{Byte: 5, Line: 1, Column: 6},
},
},
},
},
MissingItemRange: zcl.Range{
Start: zcl.Pos{Line: 1, Column: 1, Byte: 0},
End: zcl.Pos{Line: 1, Column: 2, Byte: 1},
},
},
0,
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
file, diags := Parse([]byte(test.Source), "test.hcl")
if len(diags) != 0 {
t.Fatalf("diagnostics from parse: %s", diags.Error())
}
got, _, diags := file.Body.PartialContent(test.Schema)
if len(diags) != test.DiagCount {
t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
for _, diag := range diags {
t.Logf(" - %s", diag.Error())
}
}
if !reflect.DeepEqual(got, test.Want) {
t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want))
}
})
}
}
func TestBodyJustAttributes(t *testing.T) {
tests := []struct {
Source string
Want zcl.Attributes
DiagCount int
}{
{
``,
zcl.Attributes{},
0,
},
{
`foo = "a"`,
zcl.Attributes{
"foo": &zcl.Attribute{
Name: "foo",
Expr: &expression{
src: &hclast.LiteralType{
Token: hcltoken.Token{
Type: hcltoken.STRING,
Pos: hcltoken.Pos{
Offset: 6,
Line: 1,
Column: 7,
},
Text: `"a"`,
},
},
},
Range: zcl.Range{
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
},
NameRange: zcl.Range{
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
},
},
},
0,
},
{
`foo = {}`,
zcl.Attributes{
"foo": &zcl.Attribute{
Name: "foo",
Expr: &expression{
src: &hclast.ObjectType{
List: &hclast.ObjectList{},
Lbrace: hcltoken.Pos{
Offset: 6,
Line: 1,
Column: 7,
},
Rbrace: hcltoken.Pos{
Offset: 7,
Line: 1,
Column: 8,
},
},
},
Range: zcl.Range{
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
},
NameRange: zcl.Range{
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
},
},
},
0,
},
{
`foo {}`,
zcl.Attributes{
"foo": &zcl.Attribute{
Name: "foo",
Expr: &expression{
src: &hclast.ObjectType{
List: &hclast.ObjectList{},
Lbrace: hcltoken.Pos{
Offset: 4,
Line: 1,
Column: 5,
},
Rbrace: hcltoken.Pos{
Offset: 5,
Line: 1,
Column: 6,
},
},
},
Range: zcl.Range{
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
},
NameRange: zcl.Range{
Start: zcl.Pos{Byte: 0, Line: 1, Column: 1},
End: zcl.Pos{Byte: 1, Line: 1, Column: 2},
},
},
},
1, // warning about using block syntax
},
{
`foo "bar" {}`,
zcl.Attributes{},
1, // blocks are not allowed here
},
{
`
foo = 1
foo = 2
`,
zcl.Attributes{
"foo": &zcl.Attribute{
Name: "foo",
Expr: &expression{
src: &hclast.LiteralType{
Token: hcltoken.Token{
Type: hcltoken.NUMBER,
Pos: hcltoken.Pos{
Offset: 14,
Line: 2,
Column: 14,
},
Text: `1`,
},
},
},
Range: zcl.Range{
Start: zcl.Pos{Byte: 8, Line: 2, Column: 8},
End: zcl.Pos{Byte: 9, Line: 2, Column: 9},
},
NameRange: zcl.Range{
Start: zcl.Pos{Byte: 8, Line: 2, Column: 8},
End: zcl.Pos{Byte: 9, Line: 2, Column: 9},
},
},
},
1, // duplicate definition of foo
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
file, diags := Parse([]byte(test.Source), "test.hcl")
if len(diags) != 0 {
t.Fatalf("diagnostics from parse: %s", diags.Error())
}
got, diags := file.Body.JustAttributes()
if len(diags) != test.DiagCount {
t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
for _, diag := range diags {
t.Logf(" - %s", diag.Error())
}
}
if !reflect.DeepEqual(got, test.Want) {
t.Errorf("wrong result\ngot: %s\nwant: %s", spew.Sdump(got), spew.Sdump(test.Want))
}
})
}
}
func TestExpressionValue(t *testing.T) {
tests := []struct {
Source string // HCL source assigning a value to attribute "v"
Want cty.Value
DiagCount int
}{
{
`v = 1`,
cty.NumberIntVal(1),
0,
},
{
`v = 1.5`,
cty.NumberFloatVal(1.5),
0,
},
{
`v = "hello"`,
cty.StringVal("hello"),
0,
},
{
`v = <<EOT
heredoc
EOT
`,
cty.StringVal("heredoc\n"),
0,
},
{
`v = true`,
cty.True,
0,
},
{
`v = false`,
cty.False,
0,
},
{
`v = []`,
cty.EmptyTupleVal,
0,
},
{
`v = ["hello", 5, true, 3.4]`,
cty.TupleVal([]cty.Value{
cty.StringVal("hello"),
cty.NumberIntVal(5),
cty.True,
cty.NumberFloatVal(3.4),
}),
0,
},
{
`v = {}`,
cty.EmptyObjectVal,
0,
},
{
`v = {
string = "hello"
int = 5
bool = true
float = 3.4
list = []
object = {}
}`,
cty.ObjectVal(map[string]cty.Value{
"string": cty.StringVal("hello"),
"int": cty.NumberIntVal(5),
"bool": cty.True,
"float": cty.NumberFloatVal(3.4),
"list": cty.EmptyTupleVal,
"object": cty.EmptyObjectVal,
}),
0,
},
{
`v {}`,
cty.EmptyObjectVal,
0, // warns about using block syntax during content extraction, but we ignore that here
},
}
for i, test := range tests {
t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) {
file, diags := Parse([]byte(test.Source), "test.hcl")
if len(diags) != 0 {
t.Fatalf("diagnostics from parse: %s", diags.Error())
}
content, diags := file.Body.Content(&zcl.BodySchema{
Attributes: []zcl.AttributeSchema{
{
Name: "v",
Required: true,
},
},
})
expr := content.Attributes["v"].Expr
got, diags := expr.Value(nil)
if len(diags) != test.DiagCount {
t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.DiagCount)
for _, diag := range diags {
t.Logf(" - %s", diag.Error())
}
}
if !got.RawEquals(test.Want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
}
})
}
}

View File

@ -1,450 +0,0 @@
package hclhil
import (
"fmt"
"strconv"
"strings"
"github.com/hashicorp/hcl2/zcl"
"github.com/hashicorp/hil"
hilast "github.com/hashicorp/hil/ast"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
)
func parseTemplate(src []byte, filename string, startPos zcl.Pos) (zcl.Expression, zcl.Diagnostics) {
hilStartPos := hilast.Pos{
Filename: filename,
Line: startPos.Line,
Column: startPos.Column,
// HIL positions don't have byte offsets, so we ignore startPos.Byte here
}
rootNode, err := hil.ParseWithPosition(string(src), hilStartPos)
if err != nil {
return nil, zcl.Diagnostics{
{
Severity: zcl.DiagError,
Summary: "Syntax error in template",
Detail: fmt.Sprintf("The template could not be parsed: %s", err),
Subject: errorRange(err),
},
}
}
return &templateExpression{
node: rootNode,
}, nil
}
type templateExpression struct {
node hilast.Node
}
func (e *templateExpression) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
cfg := hilEvalConfig(ctx)
return ctyValueFromHILNode(e.node, cfg)
}
func (e *templateExpression) Variables() []zcl.Traversal {
var vars []zcl.Traversal
e.node.Accept(func(n hilast.Node) hilast.Node {
vn, ok := n.(*hilast.VariableAccess)
if !ok {
return n
}
rawName := vn.Name
parts := strings.Split(rawName, ".")
if len(parts) == 0 {
return n
}
tr := make(zcl.Traversal, 0, len(parts))
tr = append(tr, zcl.TraverseRoot{
Name: parts[0],
SrcRange: rangeFromHILPos(n.Pos()),
})
for _, name := range parts {
if nv, err := strconv.Atoi(name); err == nil {
// Turn this into a sequence index in zcl land, to save
// callers from having to understand both HIL-style numeric
// attributes and zcl-style indices.
tr = append(tr, zcl.TraverseIndex{
Key: cty.NumberIntVal(int64(nv)),
SrcRange: rangeFromHILPos(n.Pos()),
})
continue
}
if name == "*" {
// TODO: support splat traversals, but that requires some
// more work here because we need to then accumulate the
// rest of the parts into the splat's own "Each" traversal.
continue
}
tr = append(tr, zcl.TraverseAttr{
Name: name,
SrcRange: rangeFromHILPos(n.Pos()),
})
}
vars = append(vars, tr)
return n
})
return vars
}
func (e *templateExpression) Range() zcl.Range {
return rangeFromHILPos(e.node.Pos())
}
func (e *templateExpression) StartRange() zcl.Range {
return rangeFromHILPos(e.node.Pos())
}
func hilEvalConfig(ctx *zcl.EvalContext) *hil.EvalConfig {
cfg := &hil.EvalConfig{
GlobalScope: &hilast.BasicScope{
VarMap: map[string]hilast.Variable{},
FuncMap: map[string]hilast.Function{},
},
}
if ctx == nil {
return cfg
}
if ctx.Variables != nil {
for name, val := range ctx.Variables {
cfg.GlobalScope.VarMap[name] = hilVariableForInput(hilVariableFromCtyValue(val))
}
}
if ctx.Functions != nil {
for name, hf := range ctx.Functions {
cfg.GlobalScope.FuncMap[name] = hilFunctionFromCtyFunction(hf)
}
}
return cfg
}
func ctyValueFromHILNode(node hilast.Node, cfg *hil.EvalConfig) (cty.Value, zcl.Diagnostics) {
result, err := hil.Eval(node, cfg)
if err != nil {
return cty.DynamicVal, zcl.Diagnostics{
{
Severity: zcl.DiagError,
Summary: "Template evaluation failed",
Detail: fmt.Sprintf("Error while evaluating template: %s", err),
Subject: rangeFromHILPos(node.Pos()).Ptr(),
},
}
}
return ctyValueFromHILResult(result), nil
}
func ctyValueFromHILResult(result hil.EvaluationResult) cty.Value {
switch result.Type {
case hil.TypeString:
return cty.StringVal(result.Value.(string))
case hil.TypeBool:
return cty.BoolVal(result.Value.(bool))
case hil.TypeList:
varsI := result.Value.([]interface{})
if len(varsI) == 0 {
return cty.ListValEmpty(cty.String)
}
vals := make([]cty.Value, len(varsI))
for i, varI := range varsI {
hv, err := hil.InterfaceToVariable(varI)
if err != nil {
panic("HIL returned type that can't be converted back to variable")
}
vals[i] = ctyValueFromHILVariable(hv)
}
return cty.TupleVal(vals)
case hil.TypeMap:
varsI := result.Value.(map[string]interface{})
if len(varsI) == 0 {
return cty.MapValEmpty(cty.String)
}
vals := make(map[string]cty.Value)
for key, varI := range varsI {
hv, err := hil.InterfaceToVariable(varI)
if err != nil {
panic("HIL returned type that can't be converted back to variable")
}
vals[key] = ctyValueFromHILVariable(hv)
}
return cty.ObjectVal(vals)
case hil.TypeUnknown:
// HIL doesn't have typed unknowns, so we have to return dynamic
return cty.DynamicVal
default:
// should never happen
panic(fmt.Sprintf("unsupported EvaluationResult type %s", result.Type))
}
}
func ctyValueFromHILVariable(vr hilast.Variable) cty.Value {
switch vr.Type {
case hilast.TypeBool:
return cty.BoolVal(vr.Value.(bool))
case hilast.TypeString:
return cty.StringVal(vr.Value.(string))
case hilast.TypeInt:
return cty.NumberIntVal(vr.Value.(int64))
case hilast.TypeFloat:
return cty.NumberFloatVal(vr.Value.(float64))
case hilast.TypeList:
vars := vr.Value.([]hilast.Variable)
if len(vars) == 0 {
return cty.ListValEmpty(cty.String)
}
vals := make([]cty.Value, len(vars))
for i, v := range vars {
vals[i] = ctyValueFromHILVariable(v)
}
return cty.TupleVal(vals)
case hilast.TypeMap:
vars := vr.Value.(map[string]hilast.Variable)
if len(vars) == 0 {
return cty.MapValEmpty(cty.String)
}
vals := make(map[string]cty.Value)
for key, v := range vars {
vals[key] = ctyValueFromHILVariable(v)
}
return cty.ObjectVal(vals)
case hilast.TypeAny, hilast.TypeUnknown:
return cty.DynamicVal
default:
// should never happen
panic(fmt.Sprintf("unsupported HIL Variable type %s", vr.Type))
}
}
func hilVariableFromCtyValue(val cty.Value) hilast.Variable {
if !val.IsKnown() {
return hilast.Variable{
Type: hilast.TypeUnknown,
Value: hil.UnknownValue,
}
}
if val.IsNull() {
// HIL doesn't actually support nulls, so we'll cheat a bit and
// use an unknown. This is not quite right since nulls are supposed
// to fail when evaluated, but it should suffice as a compatibility
// shim since HIL-using applications probably won't be generating
// nulls anyway.
return hilast.Variable{
Type: hilast.TypeUnknown,
Value: hil.UnknownValue,
}
}
ty := val.Type()
switch ty {
case cty.String:
return hilast.Variable{
Type: hilast.TypeString,
Value: val.AsString(),
}
case cty.Number:
// cty doesn't distinguish between floats and ints, so we'll
// just always use floats here and depend on automatic conversions
// to produce ints where needed.
bf := val.AsBigFloat()
f, _ := bf.Float64()
return hilast.Variable{
Type: hilast.TypeFloat,
Value: f,
}
case cty.Bool:
return hilast.Variable{
Type: hilast.TypeBool,
Value: val.True(),
}
}
switch {
case ty.IsListType() || ty.IsSetType() || ty.IsTupleType():
// HIL doesn't have sets, so we'll just turn them into lists
// HIL doesn't support tuples either, so any tuples without consistent
// element types will fail HIL's check for consistent types, but that's
// okay since we don't intend to change HIL semantics here.
vars := []hilast.Variable{}
it := val.ElementIterator()
for it.Next() {
_, ev := it.Element()
vars = append(vars, hilVariableFromCtyValue(ev))
}
return hilast.Variable{
Type: hilast.TypeList,
Value: vars,
}
case ty.IsMapType():
vars := map[string]hilast.Variable{}
it := val.ElementIterator()
for it.Next() {
kv, ev := it.Element()
k := kv.AsString()
vars[k] = hilVariableFromCtyValue(ev)
}
return hilast.Variable{
Type: hilast.TypeMap,
Value: vars,
}
case ty.IsObjectType():
// HIL doesn't support objects, so objects that don't have consistent
// attribute types will fail HIL's check for consistent types. That's
// okay since we don't intend to change HIL semantics here.
vars := map[string]interface{}{}
atys := ty.AttributeTypes()
for k := range atys {
vars[k] = hilVariableFromCtyValue(val.GetAttr(k))
}
return hilast.Variable{
Type: hilast.TypeMap,
Value: vars,
}
case ty.IsCapsuleType():
// Can't do anything reasonable with capsule types, so we'll just
// treat them as unknown and let the caller deal with it as an error.
return hilast.Variable{
Type: hilast.TypeUnknown,
Value: hil.UnknownValue,
}
default:
// Should never happen if we've done our job right here
panic(fmt.Sprintf("don't know how to convert %#v into a HIL variable", ty))
}
}
// hilVariableForInput constrains the given variable to be of the types HIL
// accepts as input, which entails converting all primitive types to string.
func hilVariableForInput(v hilast.Variable) hilast.Variable {
switch v.Type {
case hilast.TypeFloat:
return hilast.Variable{
Type: hilast.TypeString,
Value: strconv.FormatFloat(v.Value.(float64), 'f', -1, 64),
}
case hilast.TypeBool:
if v.Value.(bool) {
return hilast.Variable{
Type: hilast.TypeString,
Value: "true",
}
} else {
return hilast.Variable{
Type: hilast.TypeString,
Value: "false",
}
}
case hilast.TypeList:
inVars := v.Value.([]hilast.Variable)
outVars := make([]hilast.Variable, len(inVars))
for i, inVar := range inVars {
outVars[i] = hilVariableForInput(inVar)
}
return hilast.Variable{
Type: hilast.TypeList,
Value: outVars,
}
case hilast.TypeMap:
inVars := v.Value.(map[string]hilast.Variable)
outVars := make(map[string]hilast.Variable)
for k, inVar := range inVars {
outVars[k] = hilVariableForInput(inVar)
}
return hilast.Variable{
Type: hilast.TypeMap,
Value: outVars,
}
default:
return v
}
}
func hilTypeFromCtyType(ty cty.Type) hilast.Type {
switch ty {
case cty.String:
return hilast.TypeString
case cty.Number:
return hilast.TypeFloat
case cty.Bool:
return hilast.TypeBool
case cty.DynamicPseudoType:
// Assume we're using this as a type specification, so we'd rather
// have TypeAny than TypeUnknown.
return hilast.TypeAny
}
switch {
case ty.IsListType() || ty.IsSetType() || ty.IsTupleType():
return hilast.TypeList
case ty.IsMapType(), ty.IsObjectType():
return hilast.TypeMap
default:
return hilast.TypeUnknown
}
}
func hilFunctionFromCtyFunction(f function.Function) hilast.Function {
hf := hilast.Function{}
params := f.Params()
varParam := f.VarParam()
hf.ArgTypes = make([]hilast.Type, len(params))
staticTypes := make([]cty.Type, len(params))
for i, param := range params {
hf.ArgTypes[i] = hilTypeFromCtyType(param.Type)
staticTypes[i] = param.Type
}
if varParam != nil {
hf.Variadic = true
hf.VariadicType = hilTypeFromCtyType(varParam.Type)
}
retType, err := f.ReturnType(staticTypes)
if err == nil {
hf.ReturnType = hilTypeFromCtyType(retType)
} else {
hf.ReturnType = hilTypeFromCtyType(cty.DynamicPseudoType)
}
hf.Callback = func(hilArgs []interface{}) (interface{}, error) {
args := make([]cty.Value, len(hilArgs))
for i, hilArg := range hilArgs {
var hilType hilast.Type
if i < len(hf.ArgTypes) {
hilType = hf.ArgTypes[i]
} else {
hilType = hf.VariadicType
}
args[i] = ctyValueFromHILVariable(hilast.Variable{
Type: hilType,
Value: hilArg,
})
}
result, err := f.Call(args)
if err != nil {
return nil, err
}
hilResult := hilVariableFromCtyValue(result)
return hilResult.Value, nil
}
return hf
}

View File

@ -1,187 +0,0 @@
package hclhil
import (
"testing"
"github.com/hashicorp/hcl2/zcl"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/function"
"github.com/zclconf/go-cty/cty/function/stdlib"
)
func TestTemplateExpression(t *testing.T) {
tests := []struct {
input string
ctx *zcl.EvalContext
want cty.Value
diagCount int
}{
{
``,
nil,
cty.StringVal(""),
0,
},
{
`hello`,
nil,
cty.StringVal("hello"),
0,
},
{
`hello ${"world"}`,
nil,
cty.StringVal("hello world"),
0,
},
{
`${"hello"}`,
nil,
cty.StringVal("hello"),
0,
},
{
`Hello ${planet}!`,
&zcl.EvalContext{
Variables: map[string]cty.Value{
"planet": cty.StringVal("Earth"),
},
},
cty.StringVal("Hello Earth!"),
0,
},
{
`${names}`,
&zcl.EvalContext{
Variables: map[string]cty.Value{
"names": cty.ListVal([]cty.Value{
cty.StringVal("Ermintrude"),
cty.StringVal("Tom"),
}),
},
},
cty.TupleVal([]cty.Value{
cty.StringVal("Ermintrude"),
cty.StringVal("Tom"),
}),
0,
},
{
`${doodads}`,
&zcl.EvalContext{
Variables: map[string]cty.Value{
"doodads": cty.MapVal(map[string]cty.Value{
"Captain": cty.StringVal("Ermintrude"),
"First Officer": cty.StringVal("Tom"),
}),
},
},
cty.ObjectVal(map[string]cty.Value{
"Captain": cty.StringVal("Ermintrude"),
"First Officer": cty.StringVal("Tom"),
}),
0,
},
{
`${names}`,
&zcl.EvalContext{
Variables: map[string]cty.Value{
"names": cty.TupleVal([]cty.Value{
cty.StringVal("Ermintrude"),
cty.NumberIntVal(5),
}),
},
},
cty.TupleVal([]cty.Value{
cty.StringVal("Ermintrude"),
cty.StringVal("5"),
}),
0,
},
{
`${messytuple}`,
&zcl.EvalContext{
Variables: map[string]cty.Value{
"messytuple": cty.TupleVal([]cty.Value{
cty.StringVal("Ermintrude"),
cty.ListValEmpty(cty.String),
}),
},
},
cty.TupleVal([]cty.Value{
cty.StringVal("Ermintrude"),
cty.ListValEmpty(cty.String), // HIL's sloppy type checker actually lets us get away with this
}),
0,
},
{
`number ${num}`,
&zcl.EvalContext{
Variables: map[string]cty.Value{
"num": cty.NumberIntVal(5),
},
},
cty.StringVal("number 5"),
0,
},
{
`${length("hello")}`,
&zcl.EvalContext{
Functions: map[string]function.Function{
"length": stdlib.StrlenFunc,
},
},
cty.StringVal("5"), // HIL always stringifies numbers on output
0,
},
{
`${true}`,
nil,
cty.StringVal("true"), // HIL always stringifies bools on output
0,
},
{
`cannot ${names}`,
&zcl.EvalContext{
Variables: map[string]cty.Value{
"names": cty.ListVal([]cty.Value{
cty.StringVal("Ermintrude"),
cty.StringVal("Tom"),
}),
},
},
cty.DynamicVal,
1, // can't concatenate a list
},
{
`${syntax error`,
nil,
cty.NilVal,
1,
},
}
for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
expr, diags := ParseTemplate([]byte(test.input), "test.hil")
if expr != nil {
val, valDiags := expr.Value(test.ctx)
diags = append(diags, valDiags...)
if !val.RawEquals(test.want) {
t.Errorf("wrong result\ngot: %#v\nwant: %#v", val, test.want)
}
} else {
if test.want != cty.NilVal {
t.Errorf("Unexpected diagnostics during parse: %s", diags.Error())
}
}
if len(diags) != test.diagCount {
t.Errorf("wrong number of diagnostics %d; want %d", len(diags), test.diagCount)
for _, diag := range diags {
t.Logf(" - %s", diag.Error())
}
}
})
}
}

View File

@ -4,7 +4,6 @@ import (
"fmt"
"github.com/hashicorp/hcl2/zcl"
"github.com/hashicorp/hcl2/zcl/hclhil"
"github.com/hashicorp/hcl2/zcl/zclsyntax"
"github.com/zclconf/go-cty/cty"
)
@ -29,8 +28,7 @@ type body struct {
// expression is the implementation of "Expression" used for files processed
// with the JSON parser.
type expression struct {
src node
useHIL bool
src node
}
func (b *body) Content(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Diagnostics) {
@ -113,7 +111,7 @@ func (b *body) PartialContent(schema *zcl.BodySchema) (*zcl.BodyContent, zcl.Bod
}
content.Attributes[attrS.Name] = &zcl.Attribute{
Name: attrS.Name,
Expr: &expression{src: jsonAttr.Value, useHIL: b.useHIL},
Expr: &expression{src: jsonAttr.Value},
Range: zcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
NameRange: jsonAttr.NameRange,
}
@ -157,7 +155,7 @@ func (b *body) JustAttributes() (zcl.Attributes, zcl.Diagnostics) {
}
attrs[name] = &zcl.Attribute{
Name: name,
Expr: &expression{src: jsonAttr.Value, useHIL: b.useHIL},
Expr: &expression{src: jsonAttr.Value},
Range: zcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
NameRange: jsonAttr.NameRange,
}
@ -270,27 +268,6 @@ func (b *body) unpackBlock(v node, typeName string, typeRange *zcl.Range, labels
func (e *expression) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
switch v := e.src.(type) {
case *stringVal:
if e.useHIL && ctx != nil {
// Legacy interface to parse HCL-style JSON with HIL expressions.
templateSrc := v.Value
hilExpr, diags := hclhil.ParseTemplateEmbedded(
[]byte(templateSrc),
v.SrcRange.Filename,
zcl.Pos{
// skip over the opening quote mark
Byte: v.SrcRange.Start.Byte + 1,
Line: v.SrcRange.Start.Line,
Column: v.SrcRange.Start.Column,
},
)
if hilExpr == nil {
return cty.DynamicVal, diags
}
val, evalDiags := hilExpr.Value(ctx)
diags = append(diags, evalDiags...)
return val, diags
}
if ctx != nil {
// Parse string contents as a zcl native language expression.
// We only do this if we have a context, so passing a nil context
@ -330,14 +307,14 @@ func (e *expression) Value(ctx *zcl.EvalContext) (cty.Value, zcl.Diagnostics) {
case *arrayVal:
vals := []cty.Value{}
for _, jsonVal := range v.Values {
val, _ := (&expression{src: jsonVal, useHIL: e.useHIL}).Value(ctx)
val, _ := (&expression{src: jsonVal}).Value(ctx)
vals = append(vals, val)
}
return cty.TupleVal(vals), nil
case *objectVal:
attrs := map[string]cty.Value{}
for name, jsonAttr := range v.Attrs {
val, _ := (&expression{src: jsonAttr.Value, useHIL: e.useHIL}).Value(ctx)
val, _ := (&expression{src: jsonAttr.Value}).Value(ctx)
attrs[name] = val
}
return cty.ObjectVal(attrs), nil
@ -353,34 +330,16 @@ func (e *expression) Variables() []zcl.Traversal {
switch v := e.src.(type) {
case *stringVal:
if e.useHIL {
// Legacy interface to parse HCL-style JSON with HIL expressions.
templateSrc := v.Value
hilExpr, _ := hclhil.ParseTemplateEmbedded(
[]byte(templateSrc),
v.SrcRange.Filename,
zcl.Pos{
// skip over the opening quote mark
Byte: v.SrcRange.Start.Byte + 1,
Line: v.SrcRange.Start.Line,
Column: v.SrcRange.Start.Column,
},
)
if hilExpr != nil {
vars = append(vars, hilExpr.Variables()...)
}
}
// FIXME: Once the native zcl template language parser is implemented,
// parse with that and look for variables in there too,
case *arrayVal:
for _, jsonVal := range v.Values {
vars = append(vars, (&expression{src: jsonVal, useHIL: e.useHIL}).Variables()...)
vars = append(vars, (&expression{src: jsonVal}).Variables()...)
}
case *objectVal:
for _, jsonAttr := range v.Attrs {
vars = append(vars, (&expression{src: jsonAttr.Value, useHIL: e.useHIL}).Variables()...)
vars = append(vars, (&expression{src: jsonAttr.Value}).Variables()...)
}
}

View File

@ -5,7 +5,6 @@ import (
"io/ioutil"
"github.com/hashicorp/hcl2/zcl"
"github.com/hashicorp/hcl2/zcl/hclhil"
"github.com/hashicorp/hcl2/zcl/json"
"github.com/hashicorp/hcl2/zcl/zclsyntax"
)
@ -82,20 +81,6 @@ func (p *Parser) ParseJSON(src []byte, filename string) (*zcl.File, zcl.Diagnost
return file, diags
}
// ParseJSONWithHIL parses the given JSON buffer (which is assumed to have been
// loaded from the given filename) and returns the zcl.File object representing
// it. Unlike ParseJSON, the strings within the file will be interpreted as
// HIL templates rather than native zcl templates.
func (p *Parser) ParseJSONWithHIL(src []byte, filename string) (*zcl.File, zcl.Diagnostics) {
if existing := p.files[filename]; existing != nil {
return existing, nil
}
file, diags := json.ParseWithHIL(src, filename)
p.files[filename] = file
return file, diags
}
// ParseJSONFile reads the given filename and parses it as JSON, similarly to
// ParseJSON. An error diagnostic is returned if the given file cannot be read.
func (p *Parser) ParseJSONFile(filename string) (*zcl.File, zcl.Diagnostics) {
@ -108,47 +93,6 @@ func (p *Parser) ParseJSONFile(filename string) (*zcl.File, zcl.Diagnostics) {
return file, diags
}
// ParseJSONFileWithHIL reads the given filename and parses it as JSON, similarly to
// ParseJSONWithHIL. An error diagnostic is returned if the given file cannot be read.
func (p *Parser) ParseJSONFileWithHIL(filename string) (*zcl.File, zcl.Diagnostics) {
if existing := p.files[filename]; existing != nil {
return existing, nil
}
file, diags := json.ParseFileWithHIL(filename)
p.files[filename] = file
return file, diags
}
// ParseHCLHIL parses the given buffer (which is assumed to have been loaded
// from the given filename) using the HCL and HIL parsers, and returns the
// zcl.File object representing it.
//
// This HCL/HIL parser is a compatibility interface to ease migration for
// apps that previously used HCL and HIL directly.
func (p *Parser) ParseHCLHIL(src []byte, filename string) (*zcl.File, zcl.Diagnostics) {
if existing := p.files[filename]; existing != nil {
return existing, nil
}
file, diags := hclhil.Parse(src, filename)
p.files[filename] = file
return file, diags
}
// ParseHCLHILFile reads the given filename and parses it as HCL/HIL, similarly
// to ParseHCLHIL. An error diagnostic is returned if the given file cannot be
// read.
func (p *Parser) ParseHCLHILFile(filename string) (*zcl.File, zcl.Diagnostics) {
if existing := p.files[filename]; existing != nil {
return existing, nil
}
file, diags := hclhil.ParseFile(filename)
p.files[filename] = file
return file, diags
}
// AddFile allows a caller to record in a parser a file that was parsed some
// other way, thus allowing it to be included in the registry of sources.
func (p *Parser) AddFile(filename string, file *zcl.File) {