mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-07-09 21:17:09 +08:00
Modify parsing functions and config structs to accept hcl changes
Signed-off-by: Patrick Van Stee <patrick@vanstee.me>
This commit is contained in:
209
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/README.md
generated
vendored
Normal file
209
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/README.md
generated
vendored
Normal file
@ -0,0 +1,209 @@
|
||||
# HCL Custom Static Decoding Extension
|
||||
|
||||
This HCL extension provides a mechanism for defining arguments in an HCL-based
|
||||
language whose values are derived using custom decoding rules against the
|
||||
HCL expression syntax, overriding the usual behavior of normal expression
|
||||
evaluation.
|
||||
|
||||
"Arguments", for the purpose of this extension, currently includes the
|
||||
following two contexts:
|
||||
|
||||
* For applications using `hcldec` for dynamic decoding, a `hcldec.AttrSpec`
|
||||
or `hcldec.BlockAttrsSpec` can be given a special type constraint that
|
||||
opts in to custom decoding behavior for the attribute(s) that are selected
|
||||
by that specification.
|
||||
|
||||
* When working with the HCL native expression syntax, a function given in
|
||||
the `hcl.EvalContext` during evaluation can have parameters with special
|
||||
type constraints that opt in to custom decoding behavior for the argument
|
||||
expression associated with that parameter in any call.
|
||||
|
||||
The above use-cases are rather abstract, so we'll consider a motivating
|
||||
real-world example: sometimes we (language designers) need to allow users
|
||||
to specify type constraints directly in the language itself, such as in
|
||||
[Terraform's Input Variables](https://www.terraform.io/docs/configuration/variables.html).
|
||||
Terraform's `variable` blocks include an argument called `type` which takes
|
||||
a type constraint given using HCL expression building-blocks as defined by
|
||||
[the HCL `typeexpr` extension](../typeexpr/README.md).
|
||||
|
||||
A "type constraint expression" of that sort is not an expression intended to
|
||||
be evaluated in the usual way. Instead, the physical expression is
|
||||
deconstructed using [the static analysis operations](../../spec.md#static-analysis)
|
||||
to produce a `cty.Type` as the result, rather than a `cty.Value`.
|
||||
|
||||
The purpose of this Custom Static Decoding Extension, then, is to provide a
|
||||
bridge to allow that sort of custom decoding to be used via mechanisms that
|
||||
normally deal in `cty.Value`, such as `hcldec` and native syntax function
|
||||
calls as listed above.
|
||||
|
||||
(Note: [`gohcl`](https://pkg.go.dev/github.com/hashicorp/hcl/v2/gohcl) has
|
||||
its own mechanism to support this use case, exploiting the fact that it is
|
||||
working directly with "normal" Go types. Decoding into a struct field of
|
||||
type `hcl.Expression` obtains the expression directly without evaluating it
|
||||
first. The Custom Static Decoding Extension is not necessary for that `gohcl`
|
||||
technique. You can also implement custom decoding by working directly with
|
||||
the lowest-level HCL API, which separates extraction of and evaluation of
|
||||
expressions into two steps.)
|
||||
|
||||
## Custom Decoding Types
|
||||
|
||||
This extension relies on a convention implemented in terms of
|
||||
[_Capsule Types_ in the underlying `cty` type system](https://github.com/zclconf/go-cty/blob/master/docs/types.md#capsule-types). `cty` allows a capsule type to carry arbitrary
|
||||
extension metadata values as an aid to creating higher-level abstractions like
|
||||
this extension.
|
||||
|
||||
A custom argument decoding mode, then, is implemented by creating a new `cty`
|
||||
capsule type that implements the `ExtensionData` custom operation to return
|
||||
a decoding function when requested. For example:
|
||||
|
||||
```go
|
||||
var keywordType cty.Type
|
||||
keywordType = cty.CapsuleWithOps("keyword", reflect.TypeOf(""), &cty.CapsuleOps{
|
||||
ExtensionData: func(key interface{}) interface{} {
|
||||
switch key {
|
||||
case customdecode.CustomExpressionDecoder:
|
||||
return customdecode.CustomExpressionDecoderFunc(
|
||||
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
kw := hcl.ExprAsKeyword(expr)
|
||||
if kw == "" {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid keyword",
|
||||
Detail: "A keyword is required",
|
||||
Subject: expr.Range().Ptr(),
|
||||
})
|
||||
return cty.UnkownVal(keywordType), diags
|
||||
}
|
||||
return cty.CapsuleVal(keywordType, &kw)
|
||||
},
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
The boilerplate here is a bit fussy, but the important part for our purposes
|
||||
is the `case customdecode.CustomExpressionDecoder:` clause, which uses
|
||||
a custom extension key type defined in this package to recognize when a
|
||||
component implementing this extension is checking to see if a target type
|
||||
has a custom decode implementation.
|
||||
|
||||
In the above case we've defined a type that decodes expressions as static
|
||||
keywords, so a keyword like `foo` would decode as an encapsulated `"foo"`
|
||||
string, while any other sort of expression like `"baz"` or `1 + 1` would
|
||||
return an error.
|
||||
|
||||
We could then use `keywordType` as a type constraint either for a function
|
||||
parameter or a `hcldec` attribute specification, which would require the
|
||||
argument for that function parameter or the expression for the matching
|
||||
attributes to be a static keyword, rather than an arbitrary expression.
|
||||
For example, in a `hcldec.AttrSpec`:
|
||||
|
||||
```go
|
||||
keywordSpec := &hcldec.AttrSpec{
|
||||
Name: "keyword",
|
||||
Type: keywordType,
|
||||
}
|
||||
```
|
||||
|
||||
The above would accept input like the following and would set its result to
|
||||
a `cty.Value` of `keywordType`, after decoding:
|
||||
|
||||
```hcl
|
||||
keyword = foo
|
||||
```
|
||||
|
||||
## The Expression and Expression Closure `cty` types
|
||||
|
||||
Building on the above, this package also includes two capsule types that use
|
||||
the above mechanism to allow calling applications to capture expressions
|
||||
directly and thus defer analysis to a later step, after initial decoding.
|
||||
|
||||
The `customdecode.ExpressionType` type encapsulates an `hcl.Expression` alone,
|
||||
for situations like our type constraint expression example above where it's
|
||||
the static structure of the expression we want to inspect, and thus any
|
||||
variables and functions defined in the evaluation context are irrelevant.
|
||||
|
||||
The `customdecode.ExpressionClosureType` type encapsulates a
|
||||
`*customdecode.ExpressionClosure` value, which binds the given expression to
|
||||
the `hcl.EvalContext` it was asked to evaluate against and thus allows the
|
||||
receiver of that result to later perform normal evaluation of the expression
|
||||
with all the same variables and functions that would've been available to it
|
||||
naturally.
|
||||
|
||||
Both of these types can be used as type constraints either for `hcldec`
|
||||
attribute specifications or for function arguments. Here's an example of
|
||||
`ExpressionClosureType` to implement a function that can evaluate
|
||||
an expression with some additional variables defined locally, which we'll
|
||||
call the `with(...)` function:
|
||||
|
||||
```go
|
||||
var WithFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "variables",
|
||||
Type: cty.DynamicPseudoType,
|
||||
},
|
||||
{
|
||||
Name: "expression",
|
||||
Type: customdecode.ExpressionClosureType,
|
||||
},
|
||||
},
|
||||
Type: func(args []cty.Value) (cty.Type, error) {
|
||||
varsVal := args[0]
|
||||
exprVal := args[1]
|
||||
if !varsVal.Type().IsObjectType() {
|
||||
return cty.NilVal, function.NewArgErrorf(0, "must be an object defining local variables")
|
||||
}
|
||||
if !varsVal.IsKnown() {
|
||||
// We can't predict our result type until the variables object
|
||||
// is known.
|
||||
return cty.DynamicPseudoType, nil
|
||||
}
|
||||
vars := varsVal.AsValueMap()
|
||||
closure := customdecode.ExpressionClosureFromVal(exprVal)
|
||||
result, err := evalWithLocals(vars, closure)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
return result.Type(), nil
|
||||
},
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
varsVal := args[0]
|
||||
exprVal := args[1]
|
||||
vars := varsVal.AsValueMap()
|
||||
closure := customdecode.ExpressionClosureFromVal(exprVal)
|
||||
return evalWithLocals(vars, closure)
|
||||
},
|
||||
})
|
||||
|
||||
func evalWithLocals(locals map[string]cty.Value, closure *customdecode.ExpressionClosure) (cty.Value, error) {
|
||||
childCtx := closure.EvalContext.NewChild()
|
||||
childCtx.Variables = locals
|
||||
val, diags := closure.Expression.Value(childCtx)
|
||||
if diags.HasErrors() {
|
||||
return cty.NilVal, function.NewArgErrorf(1, "couldn't evaluate expression: %s", diags.Error())
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
```
|
||||
|
||||
If the above function were placed into an `hcl.EvalContext` as `with`, it
|
||||
could be used in a native syntax call to that function as follows:
|
||||
|
||||
```hcl
|
||||
foo = with({name = "Cory"}, "${greeting}, ${name}!")
|
||||
```
|
||||
|
||||
The above assumes a variable in the main context called `greeting`, to which
|
||||
the `with` function adds `name` before evaluating the expression given in
|
||||
its second argument. This makes that second argument context-sensitive -- it
|
||||
would behave differently if the user wrote the same thing somewhere else -- so
|
||||
this capability should be used with care to make sure it doesn't cause confusion
|
||||
for the end-users of your language.
|
||||
|
||||
There are some other examples of this capability to evaluate expressions in
|
||||
unusual ways in the `tryfunc` directory that is a sibling of this one.
|
56
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/customdecode.go
generated
vendored
Normal file
56
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/customdecode.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
// Package customdecode contains a HCL extension that allows, in certain
|
||||
// contexts, expression evaluation to be overridden by custom static analysis.
|
||||
//
|
||||
// This mechanism is only supported in certain specific contexts where
|
||||
// expressions are decoded with a specific target type in mind. For more
|
||||
// information, see the documentation on CustomExpressionDecoder.
|
||||
package customdecode
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
type customDecoderImpl int
|
||||
|
||||
// CustomExpressionDecoder is a value intended to be used as a cty capsule
|
||||
// type ExtensionData key for capsule types whose values are to be obtained
|
||||
// by static analysis of an expression rather than normal evaluation of that
|
||||
// expression.
|
||||
//
|
||||
// When a cooperating capsule type is asked for ExtensionData with this key,
|
||||
// it must return a non-nil CustomExpressionDecoderFunc value.
|
||||
//
|
||||
// This mechanism is not universally supported; instead, it's handled in a few
|
||||
// specific places where expressions are evaluated with the intent of producing
|
||||
// a cty.Value of a type given by the calling application.
|
||||
//
|
||||
// Specifically, this currently works for type constraints given in
|
||||
// hcldec.AttrSpec and hcldec.BlockAttrsSpec, and it works for arguments to
|
||||
// function calls in the HCL native syntax. HCL extensions implemented outside
|
||||
// of the main HCL module may also implement this; consult their own
|
||||
// documentation for details.
|
||||
const CustomExpressionDecoder = customDecoderImpl(1)
|
||||
|
||||
// CustomExpressionDecoderFunc is the type of value that must be returned by
|
||||
// a capsule type handling the key CustomExpressionDecoder in its ExtensionData
|
||||
// implementation.
|
||||
//
|
||||
// If no error diagnostics are returned, the result value MUST be of the
|
||||
// capsule type that the decoder function was derived from. If the returned
|
||||
// error diagnostics prevent producing a value at all, return cty.NilVal.
|
||||
type CustomExpressionDecoderFunc func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics)
|
||||
|
||||
// CustomExpressionDecoderForType takes any cty type and returns its
|
||||
// custom expression decoder implementation if it has one. If it is not a
|
||||
// capsule type or it does not implement a custom expression decoder, this
|
||||
// function returns nil.
|
||||
func CustomExpressionDecoderForType(ty cty.Type) CustomExpressionDecoderFunc {
|
||||
if !ty.IsCapsuleType() {
|
||||
return nil
|
||||
}
|
||||
if fn, ok := ty.CapsuleExtensionData(CustomExpressionDecoder).(CustomExpressionDecoderFunc); ok {
|
||||
return fn
|
||||
}
|
||||
return nil
|
||||
}
|
146
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/expression_type.go
generated
vendored
Normal file
146
vendor/github.com/hashicorp/hcl/v2/ext/customdecode/expression_type.go
generated
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
package customdecode
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// ExpressionType is a cty capsule type that carries hcl.Expression values.
|
||||
//
|
||||
// This type implements custom decoding in the most general way possible: it
|
||||
// just captures whatever expression is given to it, with no further processing
|
||||
// whatsoever. It could therefore be useful in situations where an application
|
||||
// must defer processing of the expression content until a later step.
|
||||
//
|
||||
// ExpressionType only captures the expression, not the evaluation context it
|
||||
// was destined to be evaluated in. That means this type can be fine for
|
||||
// situations where the recipient of the value only intends to do static
|
||||
// analysis, but ExpressionClosureType is more appropriate in situations where
|
||||
// the recipient will eventually evaluate the given expression.
|
||||
var ExpressionType cty.Type
|
||||
|
||||
// ExpressionVal returns a new cty value of type ExpressionType, wrapping the
|
||||
// given expression.
|
||||
func ExpressionVal(expr hcl.Expression) cty.Value {
|
||||
return cty.CapsuleVal(ExpressionType, &expr)
|
||||
}
|
||||
|
||||
// ExpressionFromVal returns the expression encapsulated in the given value, or
|
||||
// panics if the value is not a known value of ExpressionType.
|
||||
func ExpressionFromVal(v cty.Value) hcl.Expression {
|
||||
if !v.Type().Equals(ExpressionType) {
|
||||
panic("value is not of ExpressionType")
|
||||
}
|
||||
ptr := v.EncapsulatedValue().(*hcl.Expression)
|
||||
return *ptr
|
||||
}
|
||||
|
||||
// ExpressionClosureType is a cty capsule type that carries hcl.Expression
|
||||
// values along with their original evaluation contexts.
|
||||
//
|
||||
// This is similar to ExpressionType except that during custom decoding it
|
||||
// also captures the hcl.EvalContext that was provided, allowing callers to
|
||||
// evaluate the expression later in the same context where it would originally
|
||||
// have been evaluated, or a context derived from that one.
|
||||
var ExpressionClosureType cty.Type
|
||||
|
||||
// ExpressionClosure is the type encapsulated in ExpressionClosureType
|
||||
type ExpressionClosure struct {
|
||||
Expression hcl.Expression
|
||||
EvalContext *hcl.EvalContext
|
||||
}
|
||||
|
||||
// ExpressionClosureVal returns a new cty value of type ExpressionClosureType,
|
||||
// wrapping the given expression closure.
|
||||
func ExpressionClosureVal(closure *ExpressionClosure) cty.Value {
|
||||
return cty.CapsuleVal(ExpressionClosureType, closure)
|
||||
}
|
||||
|
||||
// Value evaluates the closure's expression using the closure's EvalContext,
|
||||
// returning the result.
|
||||
func (c *ExpressionClosure) Value() (cty.Value, hcl.Diagnostics) {
|
||||
return c.Expression.Value(c.EvalContext)
|
||||
}
|
||||
|
||||
// ExpressionClosureFromVal returns the expression closure encapsulated in the
|
||||
// given value, or panics if the value is not a known value of
|
||||
// ExpressionClosureType.
|
||||
//
|
||||
// The caller MUST NOT modify the returned closure or the EvalContext inside
|
||||
// it. To derive a new EvalContext, either create a child context or make
|
||||
// a copy.
|
||||
func ExpressionClosureFromVal(v cty.Value) *ExpressionClosure {
|
||||
if !v.Type().Equals(ExpressionClosureType) {
|
||||
panic("value is not of ExpressionClosureType")
|
||||
}
|
||||
return v.EncapsulatedValue().(*ExpressionClosure)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Getting hold of a reflect.Type for hcl.Expression is a bit tricky because
|
||||
// it's an interface type, but we can do it with some indirection.
|
||||
goExpressionType := reflect.TypeOf((*hcl.Expression)(nil)).Elem()
|
||||
|
||||
ExpressionType = cty.CapsuleWithOps("expression", goExpressionType, &cty.CapsuleOps{
|
||||
ExtensionData: func(key interface{}) interface{} {
|
||||
switch key {
|
||||
case CustomExpressionDecoder:
|
||||
return CustomExpressionDecoderFunc(
|
||||
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
return ExpressionVal(expr), nil
|
||||
},
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
TypeGoString: func(_ reflect.Type) string {
|
||||
return "customdecode.ExpressionType"
|
||||
},
|
||||
GoString: func(raw interface{}) string {
|
||||
exprPtr := raw.(*hcl.Expression)
|
||||
return fmt.Sprintf("customdecode.ExpressionVal(%#v)", *exprPtr)
|
||||
},
|
||||
RawEquals: func(a, b interface{}) bool {
|
||||
aPtr := a.(*hcl.Expression)
|
||||
bPtr := b.(*hcl.Expression)
|
||||
return reflect.DeepEqual(*aPtr, *bPtr)
|
||||
},
|
||||
})
|
||||
ExpressionClosureType = cty.CapsuleWithOps("expression closure", reflect.TypeOf(ExpressionClosure{}), &cty.CapsuleOps{
|
||||
ExtensionData: func(key interface{}) interface{} {
|
||||
switch key {
|
||||
case CustomExpressionDecoder:
|
||||
return CustomExpressionDecoderFunc(
|
||||
func(expr hcl.Expression, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
return ExpressionClosureVal(&ExpressionClosure{
|
||||
Expression: expr,
|
||||
EvalContext: ctx,
|
||||
}), nil
|
||||
},
|
||||
)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
TypeGoString: func(_ reflect.Type) string {
|
||||
return "customdecode.ExpressionClosureType"
|
||||
},
|
||||
GoString: func(raw interface{}) string {
|
||||
closure := raw.(*ExpressionClosure)
|
||||
return fmt.Sprintf("customdecode.ExpressionClosureVal(%#v)", closure)
|
||||
},
|
||||
RawEquals: func(a, b interface{}) bool {
|
||||
closureA := a.(*ExpressionClosure)
|
||||
closureB := b.(*ExpressionClosure)
|
||||
// The expression itself compares by deep equality, but EvalContexts
|
||||
// conventionally compare by pointer identity, so we'll comply
|
||||
// with both conventions here by testing them separately.
|
||||
return closureA.EvalContext == closureB.EvalContext &&
|
||||
reflect.DeepEqual(closureA.Expression, closureB.Expression)
|
||||
},
|
||||
})
|
||||
}
|
322
vendor/github.com/hashicorp/hcl/v2/gohcl/decode.go
generated
vendored
Normal file
322
vendor/github.com/hashicorp/hcl/v2/gohcl/decode.go
generated
vendored
Normal file
@ -0,0 +1,322 @@
|
||||
package gohcl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
)
|
||||
|
||||
// DecodeBody extracts the configuration within the given body into the given
|
||||
// value. This value must be a non-nil pointer to either a struct or
|
||||
// a map, where in the former case the configuration will be decoded using
|
||||
// struct tags and in the latter case only attributes are allowed and their
|
||||
// values are decoded into the map.
|
||||
//
|
||||
// The given EvalContext is used to resolve any variables or functions in
|
||||
// expressions encountered while decoding. This may be nil to require only
|
||||
// constant values, for simple applications that do not support variables or
|
||||
// functions.
|
||||
//
|
||||
// The returned diagnostics should be inspected with its HasErrors method to
|
||||
// determine if the populated value is valid and complete. If error diagnostics
|
||||
// are returned then the given value may have been partially-populated but
|
||||
// may still be accessed by a careful caller for static analysis and editor
|
||||
// integration use-cases.
|
||||
func DecodeBody(body hcl.Body, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics {
|
||||
rv := reflect.ValueOf(val)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
panic(fmt.Sprintf("target value must be a pointer, not %s", rv.Type().String()))
|
||||
}
|
||||
|
||||
return decodeBodyToValue(body, ctx, rv.Elem())
|
||||
}
|
||||
|
||||
func decodeBodyToValue(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value) hcl.Diagnostics {
|
||||
et := val.Type()
|
||||
switch et.Kind() {
|
||||
case reflect.Struct:
|
||||
return decodeBodyToStruct(body, ctx, val)
|
||||
case reflect.Map:
|
||||
return decodeBodyToMap(body, ctx, val)
|
||||
default:
|
||||
panic(fmt.Sprintf("target value must be pointer to struct or map, not %s", et.String()))
|
||||
}
|
||||
}
|
||||
|
||||
func decodeBodyToStruct(body hcl.Body, ctx *hcl.EvalContext, val reflect.Value) hcl.Diagnostics {
|
||||
schema, partial := ImpliedBodySchema(val.Interface())
|
||||
|
||||
var content *hcl.BodyContent
|
||||
var leftovers hcl.Body
|
||||
var diags hcl.Diagnostics
|
||||
if partial {
|
||||
content, leftovers, diags = body.PartialContent(schema)
|
||||
} else {
|
||||
content, diags = body.Content(schema)
|
||||
}
|
||||
if content == nil {
|
||||
return diags
|
||||
}
|
||||
|
||||
tags := getFieldTags(val.Type())
|
||||
|
||||
if tags.Remain != nil {
|
||||
fieldIdx := *tags.Remain
|
||||
field := val.Type().Field(fieldIdx)
|
||||
fieldV := val.Field(fieldIdx)
|
||||
switch {
|
||||
case bodyType.AssignableTo(field.Type):
|
||||
fieldV.Set(reflect.ValueOf(leftovers))
|
||||
case attrsType.AssignableTo(field.Type):
|
||||
attrs, attrsDiags := leftovers.JustAttributes()
|
||||
if len(attrsDiags) > 0 {
|
||||
diags = append(diags, attrsDiags...)
|
||||
}
|
||||
fieldV.Set(reflect.ValueOf(attrs))
|
||||
default:
|
||||
diags = append(diags, decodeBodyToValue(leftovers, ctx, fieldV)...)
|
||||
}
|
||||
}
|
||||
|
||||
for name, fieldIdx := range tags.Attributes {
|
||||
attr := content.Attributes[name]
|
||||
field := val.Type().Field(fieldIdx)
|
||||
fieldV := val.Field(fieldIdx)
|
||||
|
||||
if attr == nil {
|
||||
if !exprType.AssignableTo(field.Type) {
|
||||
continue
|
||||
}
|
||||
|
||||
// As a special case, if the target is of type hcl.Expression then
|
||||
// we'll assign an actual expression that evalues to a cty null,
|
||||
// so the caller can deal with it within the cty realm rather
|
||||
// than within the Go realm.
|
||||
synthExpr := hcl.StaticExpr(cty.NullVal(cty.DynamicPseudoType), body.MissingItemRange())
|
||||
fieldV.Set(reflect.ValueOf(synthExpr))
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case attrType.AssignableTo(field.Type):
|
||||
fieldV.Set(reflect.ValueOf(attr))
|
||||
case exprType.AssignableTo(field.Type):
|
||||
fieldV.Set(reflect.ValueOf(attr.Expr))
|
||||
default:
|
||||
diags = append(diags, DecodeExpression(
|
||||
attr.Expr, ctx, fieldV.Addr().Interface(),
|
||||
)...)
|
||||
}
|
||||
}
|
||||
|
||||
blocksByType := content.Blocks.ByType()
|
||||
|
||||
for typeName, fieldIdx := range tags.Blocks {
|
||||
blocks := blocksByType[typeName]
|
||||
field := val.Type().Field(fieldIdx)
|
||||
|
||||
ty := field.Type
|
||||
isSlice := false
|
||||
isPtr := false
|
||||
if ty.Kind() == reflect.Slice {
|
||||
isSlice = true
|
||||
ty = ty.Elem()
|
||||
}
|
||||
if ty.Kind() == reflect.Ptr {
|
||||
isPtr = true
|
||||
ty = ty.Elem()
|
||||
}
|
||||
|
||||
if len(blocks) > 1 && !isSlice {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Duplicate %s block", typeName),
|
||||
Detail: fmt.Sprintf(
|
||||
"Only one %s block is allowed. Another was defined at %s.",
|
||||
typeName, blocks[0].DefRange.String(),
|
||||
),
|
||||
Subject: &blocks[1].DefRange,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if len(blocks) == 0 {
|
||||
if isSlice || isPtr {
|
||||
if val.Field(fieldIdx).IsNil() {
|
||||
val.Field(fieldIdx).Set(reflect.Zero(field.Type))
|
||||
}
|
||||
} else {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Missing %s block", typeName),
|
||||
Detail: fmt.Sprintf("A %s block is required.", typeName),
|
||||
Subject: body.MissingItemRange().Ptr(),
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
|
||||
case isSlice:
|
||||
elemType := ty
|
||||
if isPtr {
|
||||
elemType = reflect.PtrTo(ty)
|
||||
}
|
||||
sli := val.Field(fieldIdx)
|
||||
if sli.IsNil() {
|
||||
sli = reflect.MakeSlice(reflect.SliceOf(elemType), len(blocks), len(blocks))
|
||||
}
|
||||
|
||||
for i, block := range blocks {
|
||||
if isPtr {
|
||||
if i >= sli.Len() {
|
||||
sli = reflect.Append(sli, reflect.New(ty))
|
||||
}
|
||||
v := sli.Index(i)
|
||||
if v.IsNil() {
|
||||
v = reflect.New(ty)
|
||||
}
|
||||
diags = append(diags, decodeBlockToValue(block, ctx, v.Elem())...)
|
||||
sli.Index(i).Set(v)
|
||||
} else {
|
||||
diags = append(diags, decodeBlockToValue(block, ctx, sli.Index(i))...)
|
||||
}
|
||||
}
|
||||
|
||||
if sli.Len() > len(blocks) {
|
||||
sli.SetLen(len(blocks))
|
||||
}
|
||||
|
||||
val.Field(fieldIdx).Set(sli)
|
||||
|
||||
default:
|
||||
block := blocks[0]
|
||||
if isPtr {
|
||||
v := val.Field(fieldIdx)
|
||||
if v.IsNil() {
|
||||
v = reflect.New(ty)
|
||||
}
|
||||
diags = append(diags, decodeBlockToValue(block, ctx, v.Elem())...)
|
||||
val.Field(fieldIdx).Set(v)
|
||||
} else {
|
||||
diags = append(diags, decodeBlockToValue(block, ctx, val.Field(fieldIdx))...)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func decodeBodyToMap(body hcl.Body, ctx *hcl.EvalContext, v reflect.Value) hcl.Diagnostics {
|
||||
attrs, diags := body.JustAttributes()
|
||||
if attrs == nil {
|
||||
return diags
|
||||
}
|
||||
|
||||
mv := reflect.MakeMap(v.Type())
|
||||
|
||||
for k, attr := range attrs {
|
||||
switch {
|
||||
case attrType.AssignableTo(v.Type().Elem()):
|
||||
mv.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(attr))
|
||||
case exprType.AssignableTo(v.Type().Elem()):
|
||||
mv.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(attr.Expr))
|
||||
default:
|
||||
ev := reflect.New(v.Type().Elem())
|
||||
diags = append(diags, DecodeExpression(attr.Expr, ctx, ev.Interface())...)
|
||||
mv.SetMapIndex(reflect.ValueOf(k), ev.Elem())
|
||||
}
|
||||
}
|
||||
|
||||
v.Set(mv)
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
func decodeBlockToValue(block *hcl.Block, ctx *hcl.EvalContext, v reflect.Value) hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
ty := v.Type()
|
||||
|
||||
switch {
|
||||
case blockType.AssignableTo(ty):
|
||||
v.Elem().Set(reflect.ValueOf(block))
|
||||
case bodyType.AssignableTo(ty):
|
||||
v.Elem().Set(reflect.ValueOf(block.Body))
|
||||
case attrsType.AssignableTo(ty):
|
||||
attrs, attrsDiags := block.Body.JustAttributes()
|
||||
if len(attrsDiags) > 0 {
|
||||
diags = append(diags, attrsDiags...)
|
||||
}
|
||||
v.Elem().Set(reflect.ValueOf(attrs))
|
||||
default:
|
||||
diags = append(diags, decodeBodyToValue(block.Body, ctx, v)...)
|
||||
|
||||
if len(block.Labels) > 0 {
|
||||
blockTags := getFieldTags(ty)
|
||||
for li, lv := range block.Labels {
|
||||
lfieldIdx := blockTags.Labels[li].FieldIndex
|
||||
v.Field(lfieldIdx).Set(reflect.ValueOf(lv))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
||||
|
||||
// DecodeExpression extracts the value of the given expression into the given
|
||||
// value. This value must be something that gocty is able to decode into,
|
||||
// since the final decoding is delegated to that package.
|
||||
//
|
||||
// The given EvalContext is used to resolve any variables or functions in
|
||||
// expressions encountered while decoding. This may be nil to require only
|
||||
// constant values, for simple applications that do not support variables or
|
||||
// functions.
|
||||
//
|
||||
// The returned diagnostics should be inspected with its HasErrors method to
|
||||
// determine if the populated value is valid and complete. If error diagnostics
|
||||
// are returned then the given value may have been partially-populated but
|
||||
// may still be accessed by a careful caller for static analysis and editor
|
||||
// integration use-cases.
|
||||
func DecodeExpression(expr hcl.Expression, ctx *hcl.EvalContext, val interface{}) hcl.Diagnostics {
|
||||
srcVal, diags := expr.Value(ctx)
|
||||
|
||||
convTy, err := gocty.ImpliedType(val)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("unsuitable DecodeExpression target: %s", err))
|
||||
}
|
||||
|
||||
srcVal, err = convert.Convert(srcVal, convTy)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsuitable value type",
|
||||
Detail: fmt.Sprintf("Unsuitable value: %s", err.Error()),
|
||||
Subject: expr.StartRange().Ptr(),
|
||||
Context: expr.Range().Ptr(),
|
||||
})
|
||||
return diags
|
||||
}
|
||||
|
||||
err = gocty.FromCtyValue(srcVal, val)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsuitable value type",
|
||||
Detail: fmt.Sprintf("Unsuitable value: %s", err.Error()),
|
||||
Subject: expr.StartRange().Ptr(),
|
||||
Context: expr.Range().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
return diags
|
||||
}
|
57
vendor/github.com/hashicorp/hcl/v2/gohcl/doc.go
generated
vendored
Normal file
57
vendor/github.com/hashicorp/hcl/v2/gohcl/doc.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
// Package gohcl allows decoding HCL configurations into Go data structures.
|
||||
//
|
||||
// It provides a convenient and concise way of describing the schema for
|
||||
// configuration and then accessing the resulting data via native Go
|
||||
// types.
|
||||
//
|
||||
// A struct field tag scheme is used, similar to other decoding and
|
||||
// unmarshalling libraries. The tags are formatted as in the following example:
|
||||
//
|
||||
// ThingType string `hcl:"thing_type,attr"`
|
||||
//
|
||||
// Within each tag there are two comma-separated tokens. The first is the
|
||||
// name of the corresponding construct in configuration, while the second
|
||||
// is a keyword giving the kind of construct expected. The following
|
||||
// kind keywords are supported:
|
||||
//
|
||||
// attr (the default) indicates that the value is to be populated from an attribute
|
||||
// block indicates that the value is to populated from a block
|
||||
// label indicates that the value is to populated from a block label
|
||||
// optional is the same as attr, but the field is optional
|
||||
// remain indicates that the value is to be populated from the remaining body after populating other fields
|
||||
//
|
||||
// "attr" fields may either be of type *hcl.Expression, in which case the raw
|
||||
// expression is assigned, or of any type accepted by gocty, in which case
|
||||
// gocty will be used to assign the value to a native Go type.
|
||||
//
|
||||
// "block" fields may be of type *hcl.Block or hcl.Body, in which case the
|
||||
// corresponding raw value is assigned, or may be a struct that recursively
|
||||
// uses the same tags. Block fields may also be slices of any of these types,
|
||||
// in which case multiple blocks of the corresponding type are decoded into
|
||||
// the slice.
|
||||
//
|
||||
// "label" fields are considered only in a struct used as the type of a field
|
||||
// marked as "block", and are used sequentially to capture the labels of
|
||||
// the blocks being decoded. In this case, the name token is used only as
|
||||
// an identifier for the label in diagnostic messages.
|
||||
//
|
||||
// "optional" fields behave like "attr" fields, but they are optional
|
||||
// and will not give parsing errors if they are missing.
|
||||
//
|
||||
// "remain" can be placed on a single field that may be either of type
|
||||
// hcl.Body or hcl.Attributes, in which case any remaining body content is
|
||||
// placed into this field for delayed processing. If no "remain" field is
|
||||
// present then any attributes or blocks not matched by another valid tag
|
||||
// will cause an error diagnostic.
|
||||
//
|
||||
// Only a subset of this tagging/typing vocabulary is supported for the
|
||||
// "Encode" family of functions. See the EncodeIntoBody docs for full details
|
||||
// on the constraints there.
|
||||
//
|
||||
// Broadly-speaking this package deals with two types of error. The first is
|
||||
// errors in the configuration itself, which are returned as diagnostics
|
||||
// written with the configuration author as the target audience. The second
|
||||
// is bugs in the calling program, such as invalid struct tags, which are
|
||||
// surfaced via panics since there can be no useful runtime handling of such
|
||||
// errors and they should certainly not be returned to the user as diagnostics.
|
||||
package gohcl
|
191
vendor/github.com/hashicorp/hcl/v2/gohcl/encode.go
generated
vendored
Normal file
191
vendor/github.com/hashicorp/hcl/v2/gohcl/encode.go
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
package gohcl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/hcl/v2/hclwrite"
|
||||
"github.com/zclconf/go-cty/cty/gocty"
|
||||
)
|
||||
|
||||
// EncodeIntoBody replaces the contents of the given hclwrite Body with
|
||||
// attributes and blocks derived from the given value, which must be a
|
||||
// struct value or a pointer to a struct value with the struct tags defined
|
||||
// in this package.
|
||||
//
|
||||
// This function can work only with fully-decoded data. It will ignore any
|
||||
// fields tagged as "remain", any fields that decode attributes into either
|
||||
// hcl.Attribute or hcl.Expression values, and any fields that decode blocks
|
||||
// into hcl.Attributes values. This function does not have enough information
|
||||
// to complete the decoding of these types.
|
||||
//
|
||||
// Any fields tagged as "label" are ignored by this function. Use EncodeAsBlock
|
||||
// to produce a whole hclwrite.Block including block labels.
|
||||
//
|
||||
// As long as a suitable value is given to encode and the destination body
|
||||
// is non-nil, this function will always complete. It will panic in case of
|
||||
// any errors in the calling program, such as passing an inappropriate type
|
||||
// or a nil body.
|
||||
//
|
||||
// The layout of the resulting HCL source is derived from the ordering of
|
||||
// the struct fields, with blank lines around nested blocks of different types.
|
||||
// Fields representing attributes should usually precede those representing
|
||||
// blocks so that the attributes can group togather in the result. For more
|
||||
// control, use the hclwrite API directly.
|
||||
func EncodeIntoBody(val interface{}, dst *hclwrite.Body) {
|
||||
rv := reflect.ValueOf(val)
|
||||
ty := rv.Type()
|
||||
if ty.Kind() == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
ty = rv.Type()
|
||||
}
|
||||
if ty.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("value is %s, not struct", ty.Kind()))
|
||||
}
|
||||
|
||||
tags := getFieldTags(ty)
|
||||
populateBody(rv, ty, tags, dst)
|
||||
}
|
||||
|
||||
// EncodeAsBlock creates a new hclwrite.Block populated with the data from
|
||||
// the given value, which must be a struct or pointer to struct with the
|
||||
// struct tags defined in this package.
|
||||
//
|
||||
// If the given struct type has fields tagged with "label" tags then they
|
||||
// will be used in order to annotate the created block with labels.
|
||||
//
|
||||
// This function has the same constraints as EncodeIntoBody and will panic
|
||||
// if they are violated.
|
||||
func EncodeAsBlock(val interface{}, blockType string) *hclwrite.Block {
|
||||
rv := reflect.ValueOf(val)
|
||||
ty := rv.Type()
|
||||
if ty.Kind() == reflect.Ptr {
|
||||
rv = rv.Elem()
|
||||
ty = rv.Type()
|
||||
}
|
||||
if ty.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("value is %s, not struct", ty.Kind()))
|
||||
}
|
||||
|
||||
tags := getFieldTags(ty)
|
||||
labels := make([]string, len(tags.Labels))
|
||||
for i, lf := range tags.Labels {
|
||||
lv := rv.Field(lf.FieldIndex)
|
||||
// We just stringify whatever we find. It should always be a string
|
||||
// but if not then we'll still do something reasonable.
|
||||
labels[i] = fmt.Sprintf("%s", lv.Interface())
|
||||
}
|
||||
|
||||
block := hclwrite.NewBlock(blockType, labels)
|
||||
populateBody(rv, ty, tags, block.Body())
|
||||
return block
|
||||
}
|
||||
|
||||
func populateBody(rv reflect.Value, ty reflect.Type, tags *fieldTags, dst *hclwrite.Body) {
|
||||
nameIdxs := make(map[string]int, len(tags.Attributes)+len(tags.Blocks))
|
||||
namesOrder := make([]string, 0, len(tags.Attributes)+len(tags.Blocks))
|
||||
for n, i := range tags.Attributes {
|
||||
nameIdxs[n] = i
|
||||
namesOrder = append(namesOrder, n)
|
||||
}
|
||||
for n, i := range tags.Blocks {
|
||||
nameIdxs[n] = i
|
||||
namesOrder = append(namesOrder, n)
|
||||
}
|
||||
sort.SliceStable(namesOrder, func(i, j int) bool {
|
||||
ni, nj := namesOrder[i], namesOrder[j]
|
||||
return nameIdxs[ni] < nameIdxs[nj]
|
||||
})
|
||||
|
||||
dst.Clear()
|
||||
|
||||
prevWasBlock := false
|
||||
for _, name := range namesOrder {
|
||||
fieldIdx := nameIdxs[name]
|
||||
field := ty.Field(fieldIdx)
|
||||
fieldTy := field.Type
|
||||
fieldVal := rv.Field(fieldIdx)
|
||||
|
||||
if fieldTy.Kind() == reflect.Ptr {
|
||||
fieldTy = fieldTy.Elem()
|
||||
fieldVal = fieldVal.Elem()
|
||||
}
|
||||
|
||||
if _, isAttr := tags.Attributes[name]; isAttr {
|
||||
|
||||
if exprType.AssignableTo(fieldTy) || attrType.AssignableTo(fieldTy) {
|
||||
continue // ignore undecoded fields
|
||||
}
|
||||
if !fieldVal.IsValid() {
|
||||
continue // ignore (field value is nil pointer)
|
||||
}
|
||||
if fieldTy.Kind() == reflect.Ptr && fieldVal.IsNil() {
|
||||
continue // ignore
|
||||
}
|
||||
if prevWasBlock {
|
||||
dst.AppendNewline()
|
||||
prevWasBlock = false
|
||||
}
|
||||
|
||||
valTy, err := gocty.ImpliedType(fieldVal.Interface())
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("cannot encode %T as HCL expression: %s", fieldVal.Interface(), err))
|
||||
}
|
||||
|
||||
val, err := gocty.ToCtyValue(fieldVal.Interface(), valTy)
|
||||
if err != nil {
|
||||
// This should never happen, since we should always be able
|
||||
// to decode into the implied type.
|
||||
panic(fmt.Sprintf("failed to encode %T as %#v: %s", fieldVal.Interface(), valTy, err))
|
||||
}
|
||||
|
||||
dst.SetAttributeValue(name, val)
|
||||
|
||||
} else { // must be a block, then
|
||||
elemTy := fieldTy
|
||||
isSeq := false
|
||||
if elemTy.Kind() == reflect.Slice || elemTy.Kind() == reflect.Array {
|
||||
isSeq = true
|
||||
elemTy = elemTy.Elem()
|
||||
}
|
||||
|
||||
if bodyType.AssignableTo(elemTy) || attrsType.AssignableTo(elemTy) {
|
||||
continue // ignore undecoded fields
|
||||
}
|
||||
prevWasBlock = false
|
||||
|
||||
if isSeq {
|
||||
l := fieldVal.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
elemVal := fieldVal.Index(i)
|
||||
if !elemVal.IsValid() {
|
||||
continue // ignore (elem value is nil pointer)
|
||||
}
|
||||
if elemTy.Kind() == reflect.Ptr && elemVal.IsNil() {
|
||||
continue // ignore
|
||||
}
|
||||
block := EncodeAsBlock(elemVal.Interface(), name)
|
||||
if !prevWasBlock {
|
||||
dst.AppendNewline()
|
||||
prevWasBlock = true
|
||||
}
|
||||
dst.AppendBlock(block)
|
||||
}
|
||||
} else {
|
||||
if !fieldVal.IsValid() {
|
||||
continue // ignore (field value is nil pointer)
|
||||
}
|
||||
if elemTy.Kind() == reflect.Ptr && fieldVal.IsNil() {
|
||||
continue // ignore
|
||||
}
|
||||
block := EncodeAsBlock(fieldVal.Interface(), name)
|
||||
if !prevWasBlock {
|
||||
dst.AppendNewline()
|
||||
prevWasBlock = true
|
||||
}
|
||||
dst.AppendBlock(block)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
174
vendor/github.com/hashicorp/hcl/v2/gohcl/schema.go
generated
vendored
Normal file
174
vendor/github.com/hashicorp/hcl/v2/gohcl/schema.go
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
package gohcl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// ImpliedBodySchema produces a hcl.BodySchema derived from the type of the
|
||||
// given value, which must be a struct value or a pointer to one. If an
|
||||
// inappropriate value is passed, this function will panic.
|
||||
//
|
||||
// The second return argument indicates whether the given struct includes
|
||||
// a "remain" field, and thus the returned schema is non-exhaustive.
|
||||
//
|
||||
// This uses the tags on the fields of the struct to discover how each
|
||||
// field's value should be expressed within configuration. If an invalid
|
||||
// mapping is attempted, this function will panic.
|
||||
func ImpliedBodySchema(val interface{}) (schema *hcl.BodySchema, partial bool) {
|
||||
ty := reflect.TypeOf(val)
|
||||
|
||||
if ty.Kind() == reflect.Ptr {
|
||||
ty = ty.Elem()
|
||||
}
|
||||
|
||||
if ty.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("given value must be struct, not %T", val))
|
||||
}
|
||||
|
||||
var attrSchemas []hcl.AttributeSchema
|
||||
var blockSchemas []hcl.BlockHeaderSchema
|
||||
|
||||
tags := getFieldTags(ty)
|
||||
|
||||
attrNames := make([]string, 0, len(tags.Attributes))
|
||||
for n := range tags.Attributes {
|
||||
attrNames = append(attrNames, n)
|
||||
}
|
||||
sort.Strings(attrNames)
|
||||
for _, n := range attrNames {
|
||||
idx := tags.Attributes[n]
|
||||
optional := tags.Optional[n]
|
||||
field := ty.Field(idx)
|
||||
|
||||
var required bool
|
||||
|
||||
switch {
|
||||
case field.Type.AssignableTo(exprType):
|
||||
// If we're decoding to hcl.Expression then absense can be
|
||||
// indicated via a null value, so we don't specify that
|
||||
// the field is required during decoding.
|
||||
required = false
|
||||
case field.Type.Kind() != reflect.Ptr && !optional:
|
||||
required = true
|
||||
default:
|
||||
required = false
|
||||
}
|
||||
|
||||
attrSchemas = append(attrSchemas, hcl.AttributeSchema{
|
||||
Name: n,
|
||||
Required: required,
|
||||
})
|
||||
}
|
||||
|
||||
blockNames := make([]string, 0, len(tags.Blocks))
|
||||
for n := range tags.Blocks {
|
||||
blockNames = append(blockNames, n)
|
||||
}
|
||||
sort.Strings(blockNames)
|
||||
for _, n := range blockNames {
|
||||
idx := tags.Blocks[n]
|
||||
field := ty.Field(idx)
|
||||
fty := field.Type
|
||||
if fty.Kind() == reflect.Slice {
|
||||
fty = fty.Elem()
|
||||
}
|
||||
if fty.Kind() == reflect.Ptr {
|
||||
fty = fty.Elem()
|
||||
}
|
||||
if fty.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf(
|
||||
"hcl 'block' tag kind cannot be applied to %s field %s: struct required", field.Type.String(), field.Name,
|
||||
))
|
||||
}
|
||||
ftags := getFieldTags(fty)
|
||||
var labelNames []string
|
||||
if len(ftags.Labels) > 0 {
|
||||
labelNames = make([]string, len(ftags.Labels))
|
||||
for i, l := range ftags.Labels {
|
||||
labelNames[i] = l.Name
|
||||
}
|
||||
}
|
||||
|
||||
blockSchemas = append(blockSchemas, hcl.BlockHeaderSchema{
|
||||
Type: n,
|
||||
LabelNames: labelNames,
|
||||
})
|
||||
}
|
||||
|
||||
partial = tags.Remain != nil
|
||||
schema = &hcl.BodySchema{
|
||||
Attributes: attrSchemas,
|
||||
Blocks: blockSchemas,
|
||||
}
|
||||
return schema, partial
|
||||
}
|
||||
|
||||
type fieldTags struct {
|
||||
Attributes map[string]int
|
||||
Blocks map[string]int
|
||||
Labels []labelField
|
||||
Remain *int
|
||||
Optional map[string]bool
|
||||
}
|
||||
|
||||
type labelField struct {
|
||||
FieldIndex int
|
||||
Name string
|
||||
}
|
||||
|
||||
func getFieldTags(ty reflect.Type) *fieldTags {
|
||||
ret := &fieldTags{
|
||||
Attributes: map[string]int{},
|
||||
Blocks: map[string]int{},
|
||||
Optional: map[string]bool{},
|
||||
}
|
||||
|
||||
ct := ty.NumField()
|
||||
for i := 0; i < ct; i++ {
|
||||
field := ty.Field(i)
|
||||
tag := field.Tag.Get("hcl")
|
||||
if tag == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
comma := strings.Index(tag, ",")
|
||||
var name, kind string
|
||||
if comma != -1 {
|
||||
name = tag[:comma]
|
||||
kind = tag[comma+1:]
|
||||
} else {
|
||||
name = tag
|
||||
kind = "attr"
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case "attr":
|
||||
ret.Attributes[name] = i
|
||||
case "block":
|
||||
ret.Blocks[name] = i
|
||||
case "label":
|
||||
ret.Labels = append(ret.Labels, labelField{
|
||||
FieldIndex: i,
|
||||
Name: name,
|
||||
})
|
||||
case "remain":
|
||||
if ret.Remain != nil {
|
||||
panic("only one 'remain' tag is permitted")
|
||||
}
|
||||
idx := i // copy, because this loop will continue assigning to i
|
||||
ret.Remain = &idx
|
||||
case "optional":
|
||||
ret.Attributes[name] = i
|
||||
ret.Optional[name] = true
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid hcl field tag kind %q on %s %q", kind, field.Type.String(), field.Name))
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
16
vendor/github.com/hashicorp/hcl/v2/gohcl/types.go
generated
vendored
Normal file
16
vendor/github.com/hashicorp/hcl/v2/gohcl/types.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
package gohcl
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
var victimExpr hcl.Expression
|
||||
var victimBody hcl.Body
|
||||
|
||||
var exprType = reflect.TypeOf(&victimExpr).Elem()
|
||||
var bodyType = reflect.TypeOf(&victimBody).Elem()
|
||||
var blockType = reflect.TypeOf((*hcl.Block)(nil))
|
||||
var attrType = reflect.TypeOf((*hcl.Attribute)(nil))
|
||||
var attrsType = reflect.TypeOf(hcl.Attributes(nil))
|
108
vendor/github.com/hashicorp/hcl/v2/hclsimple/hclsimple.go
generated
vendored
Normal file
108
vendor/github.com/hashicorp/hcl/v2/hclsimple/hclsimple.go
generated
vendored
Normal file
@ -0,0 +1,108 @@
|
||||
// Package hclsimple is a higher-level entry point for loading HCL
|
||||
// configuration files directly into Go struct values in a single step.
|
||||
//
|
||||
// This package is more opinionated than the rest of the HCL API. See the
|
||||
// documentation for function Decode for more information.
|
||||
package hclsimple
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/gohcl"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/hashicorp/hcl/v2/json"
|
||||
)
|
||||
|
||||
// Decode parses, decodes, and evaluates expressions in the given HCL source
|
||||
// code, in a single step.
|
||||
//
|
||||
// The main HCL API is built to allow applications that need to decompose
|
||||
// the processing steps into a pipeline, with different tasks done by
|
||||
// different parts of the program: parsing the source code into an abstract
|
||||
// representation, analysing the block structure, evaluating expressions,
|
||||
// and then extracting the results into a form consumable by the rest of
|
||||
// the program.
|
||||
//
|
||||
// This function does all of those steps in one call, going directly from
|
||||
// source code to a populated Go struct value.
|
||||
//
|
||||
// The "filename" and "src" arguments describe the input configuration. The
|
||||
// filename is used to add source location context to any returned error
|
||||
// messages and its suffix will choose one of the two supported syntaxes:
|
||||
// ".hcl" for native syntax, and ".json" for HCL JSON. The src must therefore
|
||||
// contain a sequence of bytes that is valid for the selected syntax.
|
||||
//
|
||||
// The "ctx" argument provides variables and functions for use during
|
||||
// expression evaluation. Applications that need no variables nor functions
|
||||
// can just pass nil.
|
||||
//
|
||||
// The "target" argument must be a pointer to a value of a struct type,
|
||||
// with struct tags as defined by the sibling package "gohcl".
|
||||
//
|
||||
// The return type is error but any non-nil error is guaranteed to be
|
||||
// type-assertable to hcl.Diagnostics for applications that wish to access
|
||||
// the full error details.
|
||||
//
|
||||
// This is a very opinionated function that is intended to serve the needs of
|
||||
// applications that are just using HCL for simple configuration and don't
|
||||
// need detailed control over the decoding process. Because this function is
|
||||
// just wrapping functionality elsewhere, if it doesn't meet your needs then
|
||||
// please consider copying it into your program and adapting it as needed.
|
||||
func Decode(filename string, src []byte, ctx *hcl.EvalContext, target interface{}) error {
|
||||
var file *hcl.File
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
switch suffix := strings.ToLower(filepath.Ext(filename)); suffix {
|
||||
case ".hcl":
|
||||
file, diags = hclsyntax.ParseConfig(src, filename, hcl.Pos{Line: 1, Column: 1})
|
||||
case ".json":
|
||||
file, diags = json.Parse(src, filename)
|
||||
default:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported file format",
|
||||
Detail: fmt.Sprintf("Cannot read from %s: unrecognized file format suffix %q.", filename, suffix),
|
||||
})
|
||||
return diags
|
||||
}
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
|
||||
diags = gohcl.DecodeBody(file.Body, ctx, target)
|
||||
if diags.HasErrors() {
|
||||
return diags
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeFile is a wrapper around Decode that first reads the given filename
|
||||
// from disk. See the Decode documentation for more information.
|
||||
func DecodeFile(filename string, ctx *hcl.EvalContext, target interface{}) error {
|
||||
src, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Configuration file not found",
|
||||
Detail: fmt.Sprintf("The configuration file %s does not exist.", filename),
|
||||
},
|
||||
}
|
||||
}
|
||||
return hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Failed to read configuration",
|
||||
Detail: fmt.Sprintf("Can't read %s: %s.", filename, err),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return Decode(filename, src, ctx, target)
|
||||
}
|
23
vendor/github.com/hashicorp/hcl/v2/hclsyntax/diagnostics.go
generated
vendored
Normal file
23
vendor/github.com/hashicorp/hcl/v2/hclsyntax/diagnostics.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// setDiagEvalContext is an internal helper that will impose a particular
|
||||
// EvalContext on a set of diagnostics in-place, for any diagnostic that
|
||||
// does not already have an EvalContext set.
|
||||
//
|
||||
// We generally expect diagnostics to be immutable, but this is safe to use
|
||||
// on any Diagnostics where none of the contained Diagnostic objects have yet
|
||||
// been seen by a caller. Its purpose is to apply additional context to a
|
||||
// set of diagnostics produced by a "deeper" component as the stack unwinds
|
||||
// during expression evaluation.
|
||||
func setDiagEvalContext(diags hcl.Diagnostics, expr hcl.Expression, ctx *hcl.EvalContext) {
|
||||
for _, diag := range diags {
|
||||
if diag.Expression == nil {
|
||||
diag.Expression = expr
|
||||
diag.EvalContext = ctx
|
||||
}
|
||||
}
|
||||
}
|
24
vendor/github.com/hashicorp/hcl/v2/hclsyntax/didyoumean.go
generated
vendored
Normal file
24
vendor/github.com/hashicorp/hcl/v2/hclsyntax/didyoumean.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"github.com/agext/levenshtein"
|
||||
)
|
||||
|
||||
// nameSuggestion tries to find a name from the given slice of suggested names
|
||||
// that is close to the given name and returns it if found. If no suggestion
|
||||
// is close enough, returns the empty string.
|
||||
//
|
||||
// The suggestions are tried in order, so earlier suggestions take precedence
|
||||
// if the given string is similar to two or more suggestions.
|
||||
//
|
||||
// This function is intended to be used with a relatively-small number of
|
||||
// suggestions. It's not optimized for hundreds or thousands of them.
|
||||
func nameSuggestion(given string, suggestions []string) string {
|
||||
for _, suggestion := range suggestions {
|
||||
dist := levenshtein.Distance(given, suggestion, nil)
|
||||
if dist < 3 { // threshold determined experimentally
|
||||
return suggestion
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
7
vendor/github.com/hashicorp/hcl/v2/hclsyntax/doc.go
generated
vendored
Normal file
7
vendor/github.com/hashicorp/hcl/v2/hclsyntax/doc.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
// Package hclsyntax contains the parser, AST, etc for HCL's native language,
|
||||
// as opposed to the JSON variant.
|
||||
//
|
||||
// In normal use applications should rarely depend on this package directly,
|
||||
// instead preferring the higher-level interface of the main hcl package and
|
||||
// its companion package hclparse.
|
||||
package hclsyntax
|
1491
vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression.go
generated
vendored
Normal file
1491
vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
268
vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_ops.go
generated
vendored
Normal file
268
vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_ops.go
generated
vendored
Normal file
@ -0,0 +1,268 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/function/stdlib"
|
||||
)
|
||||
|
||||
type Operation struct {
|
||||
Impl function.Function
|
||||
Type cty.Type
|
||||
}
|
||||
|
||||
var (
|
||||
OpLogicalOr = &Operation{
|
||||
Impl: stdlib.OrFunc,
|
||||
Type: cty.Bool,
|
||||
}
|
||||
OpLogicalAnd = &Operation{
|
||||
Impl: stdlib.AndFunc,
|
||||
Type: cty.Bool,
|
||||
}
|
||||
OpLogicalNot = &Operation{
|
||||
Impl: stdlib.NotFunc,
|
||||
Type: cty.Bool,
|
||||
}
|
||||
|
||||
OpEqual = &Operation{
|
||||
Impl: stdlib.EqualFunc,
|
||||
Type: cty.Bool,
|
||||
}
|
||||
OpNotEqual = &Operation{
|
||||
Impl: stdlib.NotEqualFunc,
|
||||
Type: cty.Bool,
|
||||
}
|
||||
|
||||
OpGreaterThan = &Operation{
|
||||
Impl: stdlib.GreaterThanFunc,
|
||||
Type: cty.Bool,
|
||||
}
|
||||
OpGreaterThanOrEqual = &Operation{
|
||||
Impl: stdlib.GreaterThanOrEqualToFunc,
|
||||
Type: cty.Bool,
|
||||
}
|
||||
OpLessThan = &Operation{
|
||||
Impl: stdlib.LessThanFunc,
|
||||
Type: cty.Bool,
|
||||
}
|
||||
OpLessThanOrEqual = &Operation{
|
||||
Impl: stdlib.LessThanOrEqualToFunc,
|
||||
Type: cty.Bool,
|
||||
}
|
||||
|
||||
OpAdd = &Operation{
|
||||
Impl: stdlib.AddFunc,
|
||||
Type: cty.Number,
|
||||
}
|
||||
OpSubtract = &Operation{
|
||||
Impl: stdlib.SubtractFunc,
|
||||
Type: cty.Number,
|
||||
}
|
||||
OpMultiply = &Operation{
|
||||
Impl: stdlib.MultiplyFunc,
|
||||
Type: cty.Number,
|
||||
}
|
||||
OpDivide = &Operation{
|
||||
Impl: stdlib.DivideFunc,
|
||||
Type: cty.Number,
|
||||
}
|
||||
OpModulo = &Operation{
|
||||
Impl: stdlib.ModuloFunc,
|
||||
Type: cty.Number,
|
||||
}
|
||||
OpNegate = &Operation{
|
||||
Impl: stdlib.NegateFunc,
|
||||
Type: cty.Number,
|
||||
}
|
||||
)
|
||||
|
||||
var binaryOps []map[TokenType]*Operation
|
||||
|
||||
func init() {
|
||||
// This operation table maps from the operator's token type
|
||||
// to the AST operation type. All expressions produced from
|
||||
// binary operators are BinaryOp nodes.
|
||||
//
|
||||
// Binary operator groups are listed in order of precedence, with
|
||||
// the *lowest* precedence first. Operators within the same group
|
||||
// have left-to-right associativity.
|
||||
binaryOps = []map[TokenType]*Operation{
|
||||
{
|
||||
TokenOr: OpLogicalOr,
|
||||
},
|
||||
{
|
||||
TokenAnd: OpLogicalAnd,
|
||||
},
|
||||
{
|
||||
TokenEqualOp: OpEqual,
|
||||
TokenNotEqual: OpNotEqual,
|
||||
},
|
||||
{
|
||||
TokenGreaterThan: OpGreaterThan,
|
||||
TokenGreaterThanEq: OpGreaterThanOrEqual,
|
||||
TokenLessThan: OpLessThan,
|
||||
TokenLessThanEq: OpLessThanOrEqual,
|
||||
},
|
||||
{
|
||||
TokenPlus: OpAdd,
|
||||
TokenMinus: OpSubtract,
|
||||
},
|
||||
{
|
||||
TokenStar: OpMultiply,
|
||||
TokenSlash: OpDivide,
|
||||
TokenPercent: OpModulo,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type BinaryOpExpr struct {
|
||||
LHS Expression
|
||||
Op *Operation
|
||||
RHS Expression
|
||||
|
||||
SrcRange hcl.Range
|
||||
}
|
||||
|
||||
func (e *BinaryOpExpr) walkChildNodes(w internalWalkFunc) {
|
||||
w(e.LHS)
|
||||
w(e.RHS)
|
||||
}
|
||||
|
||||
func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
impl := e.Op.Impl // assumed to be a function taking exactly two arguments
|
||||
params := impl.Params()
|
||||
lhsParam := params[0]
|
||||
rhsParam := params[1]
|
||||
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
givenLHSVal, lhsDiags := e.LHS.Value(ctx)
|
||||
givenRHSVal, rhsDiags := e.RHS.Value(ctx)
|
||||
diags = append(diags, lhsDiags...)
|
||||
diags = append(diags, rhsDiags...)
|
||||
|
||||
lhsVal, err := convert.Convert(givenLHSVal, lhsParam.Type)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid operand",
|
||||
Detail: fmt.Sprintf("Unsuitable value for left operand: %s.", err),
|
||||
Subject: e.LHS.Range().Ptr(),
|
||||
Context: &e.SrcRange,
|
||||
Expression: e.LHS,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
}
|
||||
rhsVal, err := convert.Convert(givenRHSVal, rhsParam.Type)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid operand",
|
||||
Detail: fmt.Sprintf("Unsuitable value for right operand: %s.", err),
|
||||
Subject: e.RHS.Range().Ptr(),
|
||||
Context: &e.SrcRange,
|
||||
Expression: e.RHS,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
}
|
||||
|
||||
if diags.HasErrors() {
|
||||
// Don't actually try the call if we have errors already, since the
|
||||
// this will probably just produce a confusing duplicative diagnostic.
|
||||
return cty.UnknownVal(e.Op.Type), diags
|
||||
}
|
||||
|
||||
args := []cty.Value{lhsVal, rhsVal}
|
||||
result, err := impl.Call(args)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
// FIXME: This diagnostic is useless.
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Operation failed",
|
||||
Detail: fmt.Sprintf("Error during operation: %s.", err),
|
||||
Subject: &e.SrcRange,
|
||||
Expression: e,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
return cty.UnknownVal(e.Op.Type), diags
|
||||
}
|
||||
|
||||
return result, diags
|
||||
}
|
||||
|
||||
func (e *BinaryOpExpr) Range() hcl.Range {
|
||||
return e.SrcRange
|
||||
}
|
||||
|
||||
func (e *BinaryOpExpr) StartRange() hcl.Range {
|
||||
return e.LHS.StartRange()
|
||||
}
|
||||
|
||||
type UnaryOpExpr struct {
|
||||
Op *Operation
|
||||
Val Expression
|
||||
|
||||
SrcRange hcl.Range
|
||||
SymbolRange hcl.Range
|
||||
}
|
||||
|
||||
func (e *UnaryOpExpr) walkChildNodes(w internalWalkFunc) {
|
||||
w(e.Val)
|
||||
}
|
||||
|
||||
func (e *UnaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
impl := e.Op.Impl // assumed to be a function taking exactly one argument
|
||||
params := impl.Params()
|
||||
param := params[0]
|
||||
|
||||
givenVal, diags := e.Val.Value(ctx)
|
||||
|
||||
val, err := convert.Convert(givenVal, param.Type)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid operand",
|
||||
Detail: fmt.Sprintf("Unsuitable value for unary operand: %s.", err),
|
||||
Subject: e.Val.Range().Ptr(),
|
||||
Context: &e.SrcRange,
|
||||
Expression: e.Val,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
}
|
||||
|
||||
if diags.HasErrors() {
|
||||
// Don't actually try the call if we have errors already, since the
|
||||
// this will probably just produce a confusing duplicative diagnostic.
|
||||
return cty.UnknownVal(e.Op.Type), diags
|
||||
}
|
||||
|
||||
args := []cty.Value{val}
|
||||
result, err := impl.Call(args)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
// FIXME: This diagnostic is useless.
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Operation failed",
|
||||
Detail: fmt.Sprintf("Error during operation: %s.", err),
|
||||
Subject: &e.SrcRange,
|
||||
Expression: e,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
return cty.UnknownVal(e.Op.Type), diags
|
||||
}
|
||||
|
||||
return result, diags
|
||||
}
|
||||
|
||||
func (e *UnaryOpExpr) Range() hcl.Range {
|
||||
return e.SrcRange
|
||||
}
|
||||
|
||||
func (e *UnaryOpExpr) StartRange() hcl.Range {
|
||||
return e.SymbolRange
|
||||
}
|
220
vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_template.go
generated
vendored
Normal file
220
vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_template.go
generated
vendored
Normal file
@ -0,0 +1,220 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
)
|
||||
|
||||
type TemplateExpr struct {
|
||||
Parts []Expression
|
||||
|
||||
SrcRange hcl.Range
|
||||
}
|
||||
|
||||
func (e *TemplateExpr) walkChildNodes(w internalWalkFunc) {
|
||||
for _, part := range e.Parts {
|
||||
w(part)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *TemplateExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
buf := &bytes.Buffer{}
|
||||
var diags hcl.Diagnostics
|
||||
isKnown := true
|
||||
|
||||
for _, part := range e.Parts {
|
||||
partVal, partDiags := part.Value(ctx)
|
||||
diags = append(diags, partDiags...)
|
||||
|
||||
if partVal.IsNull() {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid template interpolation value",
|
||||
Detail: fmt.Sprintf(
|
||||
"The expression result is null. Cannot include a null value in a string template.",
|
||||
),
|
||||
Subject: part.Range().Ptr(),
|
||||
Context: &e.SrcRange,
|
||||
Expression: part,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if !partVal.IsKnown() {
|
||||
// If any part is unknown then the result as a whole must be
|
||||
// unknown too. We'll keep on processing the rest of the parts
|
||||
// anyway, because we want to still emit any diagnostics resulting
|
||||
// from evaluating those.
|
||||
isKnown = false
|
||||
continue
|
||||
}
|
||||
|
||||
strVal, err := convert.Convert(partVal, cty.String)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid template interpolation value",
|
||||
Detail: fmt.Sprintf(
|
||||
"Cannot include the given value in a string template: %s.",
|
||||
err.Error(),
|
||||
),
|
||||
Subject: part.Range().Ptr(),
|
||||
Context: &e.SrcRange,
|
||||
Expression: part,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
buf.WriteString(strVal.AsString())
|
||||
}
|
||||
|
||||
if !isKnown {
|
||||
return cty.UnknownVal(cty.String), diags
|
||||
}
|
||||
|
||||
return cty.StringVal(buf.String()), diags
|
||||
}
|
||||
|
||||
func (e *TemplateExpr) Range() hcl.Range {
|
||||
return e.SrcRange
|
||||
}
|
||||
|
||||
func (e *TemplateExpr) StartRange() hcl.Range {
|
||||
return e.Parts[0].StartRange()
|
||||
}
|
||||
|
||||
// IsStringLiteral returns true if and only if the template consists only of
|
||||
// single string literal, as would be created for a simple quoted string like
|
||||
// "foo".
|
||||
//
|
||||
// If this function returns true, then calling Value on the same expression
|
||||
// with a nil EvalContext will return the literal value.
|
||||
//
|
||||
// Note that "${"foo"}", "${1}", etc aren't considered literal values for the
|
||||
// purposes of this method, because the intent of this method is to identify
|
||||
// situations where the user seems to be explicitly intending literal string
|
||||
// interpretation, not situations that result in literals as a technicality
|
||||
// of the template expression unwrapping behavior.
|
||||
func (e *TemplateExpr) IsStringLiteral() bool {
|
||||
if len(e.Parts) != 1 {
|
||||
return false
|
||||
}
|
||||
_, ok := e.Parts[0].(*LiteralValueExpr)
|
||||
return ok
|
||||
}
|
||||
|
||||
// TemplateJoinExpr is used to convert tuples of strings produced by template
|
||||
// constructs (i.e. for loops) into flat strings, by converting the values
|
||||
// tos strings and joining them. This AST node is not used directly; it's
|
||||
// produced as part of the AST of a "for" loop in a template.
|
||||
type TemplateJoinExpr struct {
|
||||
Tuple Expression
|
||||
}
|
||||
|
||||
func (e *TemplateJoinExpr) walkChildNodes(w internalWalkFunc) {
|
||||
w(e.Tuple)
|
||||
}
|
||||
|
||||
func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
tuple, diags := e.Tuple.Value(ctx)
|
||||
|
||||
if tuple.IsNull() {
|
||||
// This indicates a bug in the code that constructed the AST.
|
||||
panic("TemplateJoinExpr got null tuple")
|
||||
}
|
||||
if tuple.Type() == cty.DynamicPseudoType {
|
||||
return cty.UnknownVal(cty.String), diags
|
||||
}
|
||||
if !tuple.Type().IsTupleType() {
|
||||
// This indicates a bug in the code that constructed the AST.
|
||||
panic("TemplateJoinExpr got non-tuple tuple")
|
||||
}
|
||||
if !tuple.IsKnown() {
|
||||
return cty.UnknownVal(cty.String), diags
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
it := tuple.ElementIterator()
|
||||
for it.Next() {
|
||||
_, val := it.Element()
|
||||
|
||||
if val.IsNull() {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid template interpolation value",
|
||||
Detail: fmt.Sprintf(
|
||||
"An iteration result is null. Cannot include a null value in a string template.",
|
||||
),
|
||||
Subject: e.Range().Ptr(),
|
||||
Expression: e,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if val.Type() == cty.DynamicPseudoType {
|
||||
return cty.UnknownVal(cty.String), diags
|
||||
}
|
||||
strVal, err := convert.Convert(val, cty.String)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid template interpolation value",
|
||||
Detail: fmt.Sprintf(
|
||||
"Cannot include one of the interpolation results into the string template: %s.",
|
||||
err.Error(),
|
||||
),
|
||||
Subject: e.Range().Ptr(),
|
||||
Expression: e,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if !val.IsKnown() {
|
||||
return cty.UnknownVal(cty.String), diags
|
||||
}
|
||||
|
||||
buf.WriteString(strVal.AsString())
|
||||
}
|
||||
|
||||
return cty.StringVal(buf.String()), diags
|
||||
}
|
||||
|
||||
func (e *TemplateJoinExpr) Range() hcl.Range {
|
||||
return e.Tuple.Range()
|
||||
}
|
||||
|
||||
func (e *TemplateJoinExpr) StartRange() hcl.Range {
|
||||
return e.Tuple.StartRange()
|
||||
}
|
||||
|
||||
// TemplateWrapExpr is used instead of a TemplateExpr when a template
|
||||
// consists _only_ of a single interpolation sequence. In that case, the
|
||||
// template's result is the single interpolation's result, verbatim with
|
||||
// no type conversions.
|
||||
type TemplateWrapExpr struct {
|
||||
Wrapped Expression
|
||||
|
||||
SrcRange hcl.Range
|
||||
}
|
||||
|
||||
func (e *TemplateWrapExpr) walkChildNodes(w internalWalkFunc) {
|
||||
w(e.Wrapped)
|
||||
}
|
||||
|
||||
func (e *TemplateWrapExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
return e.Wrapped.Value(ctx)
|
||||
}
|
||||
|
||||
func (e *TemplateWrapExpr) Range() hcl.Range {
|
||||
return e.SrcRange
|
||||
}
|
||||
|
||||
func (e *TemplateWrapExpr) StartRange() hcl.Range {
|
||||
return e.SrcRange
|
||||
}
|
76
vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_vars.go
generated
vendored
Normal file
76
vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_vars.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
package hclsyntax
|
||||
|
||||
// Generated by expression_vars_get.go. DO NOT EDIT.
|
||||
// Run 'go generate' on this package to update the set of functions here.
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
func (e *AnonSymbolExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *BinaryOpExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *ConditionalExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *ForExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *FunctionCallExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *IndexExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *LiteralValueExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *ObjectConsExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *ObjectConsKeyExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *RelativeTraversalExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *ScopeTraversalExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *SplatExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *TemplateExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *TemplateJoinExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *TemplateWrapExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *TupleConsExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
||||
|
||||
func (e *UnaryOpExpr) Variables() []hcl.Traversal {
|
||||
return Variables(e)
|
||||
}
|
20
vendor/github.com/hashicorp/hcl/v2/hclsyntax/file.go
generated
vendored
Normal file
20
vendor/github.com/hashicorp/hcl/v2/hclsyntax/file.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// File is the top-level object resulting from parsing a configuration file.
|
||||
type File struct {
|
||||
Body *Body
|
||||
Bytes []byte
|
||||
}
|
||||
|
||||
func (f *File) AsHCLFile() *hcl.File {
|
||||
return &hcl.File{
|
||||
Body: f.Body,
|
||||
Bytes: f.Bytes,
|
||||
|
||||
// TODO: The Nav object, once we have an implementation of it
|
||||
}
|
||||
}
|
9
vendor/github.com/hashicorp/hcl/v2/hclsyntax/generate.go
generated
vendored
Normal file
9
vendor/github.com/hashicorp/hcl/v2/hclsyntax/generate.go
generated
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
package hclsyntax
|
||||
|
||||
//go:generate go run expression_vars_gen.go
|
||||
//go:generate ruby unicode2ragel.rb --url=http://www.unicode.org/Public/9.0.0/ucd/DerivedCoreProperties.txt -m UnicodeDerived -p ID_Start,ID_Continue -o unicode_derived.rl
|
||||
//go:generate ragel -Z scan_tokens.rl
|
||||
//go:generate gofmt -w scan_tokens.go
|
||||
//go:generate ragel -Z scan_string_lit.rl
|
||||
//go:generate gofmt -w scan_string_lit.go
|
||||
//go:generate stringer -type TokenType -output token_type_string.go
|
21
vendor/github.com/hashicorp/hcl/v2/hclsyntax/keywords.go
generated
vendored
Normal file
21
vendor/github.com/hashicorp/hcl/v2/hclsyntax/keywords.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
type Keyword []byte
|
||||
|
||||
var forKeyword = Keyword([]byte{'f', 'o', 'r'})
|
||||
var inKeyword = Keyword([]byte{'i', 'n'})
|
||||
var ifKeyword = Keyword([]byte{'i', 'f'})
|
||||
var elseKeyword = Keyword([]byte{'e', 'l', 's', 'e'})
|
||||
var endifKeyword = Keyword([]byte{'e', 'n', 'd', 'i', 'f'})
|
||||
var endforKeyword = Keyword([]byte{'e', 'n', 'd', 'f', 'o', 'r'})
|
||||
|
||||
func (kw Keyword) TokenMatches(token Token) bool {
|
||||
if token.Type != TokenIdent {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal([]byte(kw), token.Bytes)
|
||||
}
|
59
vendor/github.com/hashicorp/hcl/v2/hclsyntax/navigation.go
generated
vendored
Normal file
59
vendor/github.com/hashicorp/hcl/v2/hclsyntax/navigation.go
generated
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
type navigation struct {
|
||||
root *Body
|
||||
}
|
||||
|
||||
// Implementation of hcled.ContextString
|
||||
func (n navigation) ContextString(offset int) string {
|
||||
// We will walk our top-level blocks until we find one that contains
|
||||
// the given offset, and then construct a representation of the header
|
||||
// of the block.
|
||||
|
||||
var block *Block
|
||||
for _, candidate := range n.root.Blocks {
|
||||
if candidate.Range().ContainsOffset(offset) {
|
||||
block = candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if block == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if len(block.Labels) == 0 {
|
||||
// Easy case!
|
||||
return block.Type
|
||||
}
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
buf.WriteString(block.Type)
|
||||
for _, label := range block.Labels {
|
||||
fmt.Fprintf(buf, " %q", label)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (n navigation) ContextDefRange(offset int) hcl.Range {
|
||||
var block *Block
|
||||
for _, candidate := range n.root.Blocks {
|
||||
if candidate.Range().ContainsOffset(offset) {
|
||||
block = candidate
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if block == nil {
|
||||
return hcl.Range{}
|
||||
}
|
||||
|
||||
return block.DefRange()
|
||||
}
|
22
vendor/github.com/hashicorp/hcl/v2/hclsyntax/node.go
generated
vendored
Normal file
22
vendor/github.com/hashicorp/hcl/v2/hclsyntax/node.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// Node is the abstract type that every AST node implements.
|
||||
//
|
||||
// This is a closed interface, so it cannot be implemented from outside of
|
||||
// this package.
|
||||
type Node interface {
|
||||
// This is the mechanism by which the public-facing walk functions
|
||||
// are implemented. Implementations should call the given function
|
||||
// for each child node and then replace that node with its return value.
|
||||
// The return value might just be the same node, for non-transforming
|
||||
// walks.
|
||||
walkChildNodes(w internalWalkFunc)
|
||||
|
||||
Range() hcl.Range
|
||||
}
|
||||
|
||||
type internalWalkFunc func(Node)
|
2055
vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser.go
generated
vendored
Normal file
2055
vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
799
vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser_template.go
generated
vendored
Normal file
799
vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser_template.go
generated
vendored
Normal file
@ -0,0 +1,799 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/apparentlymart/go-textseg/v12/textseg"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func (p *parser) ParseTemplate() (Expression, hcl.Diagnostics) {
|
||||
return p.parseTemplate(TokenEOF, false)
|
||||
}
|
||||
|
||||
func (p *parser) parseTemplate(end TokenType, flushHeredoc bool) (Expression, hcl.Diagnostics) {
|
||||
exprs, passthru, rng, diags := p.parseTemplateInner(end, flushHeredoc)
|
||||
|
||||
if passthru {
|
||||
if len(exprs) != 1 {
|
||||
panic("passthru set with len(exprs) != 1")
|
||||
}
|
||||
return &TemplateWrapExpr{
|
||||
Wrapped: exprs[0],
|
||||
SrcRange: rng,
|
||||
}, diags
|
||||
}
|
||||
|
||||
return &TemplateExpr{
|
||||
Parts: exprs,
|
||||
SrcRange: rng,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func (p *parser) parseTemplateInner(end TokenType, flushHeredoc bool) ([]Expression, bool, hcl.Range, hcl.Diagnostics) {
|
||||
parts, diags := p.parseTemplateParts(end)
|
||||
if flushHeredoc {
|
||||
flushHeredocTemplateParts(parts) // Trim off leading spaces on lines per the flush heredoc spec
|
||||
}
|
||||
tp := templateParser{
|
||||
Tokens: parts.Tokens,
|
||||
SrcRange: parts.SrcRange,
|
||||
}
|
||||
exprs, exprsDiags := tp.parseRoot()
|
||||
diags = append(diags, exprsDiags...)
|
||||
|
||||
passthru := false
|
||||
if len(parts.Tokens) == 2 { // one real token and one synthetic "end" token
|
||||
if _, isInterp := parts.Tokens[0].(*templateInterpToken); isInterp {
|
||||
passthru = true
|
||||
}
|
||||
}
|
||||
|
||||
return exprs, passthru, parts.SrcRange, diags
|
||||
}
|
||||
|
||||
type templateParser struct {
|
||||
Tokens []templateToken
|
||||
SrcRange hcl.Range
|
||||
|
||||
pos int
|
||||
}
|
||||
|
||||
func (p *templateParser) parseRoot() ([]Expression, hcl.Diagnostics) {
|
||||
var exprs []Expression
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
for {
|
||||
next := p.Peek()
|
||||
if _, isEnd := next.(*templateEndToken); isEnd {
|
||||
break
|
||||
}
|
||||
|
||||
expr, exprDiags := p.parseExpr()
|
||||
diags = append(diags, exprDiags...)
|
||||
exprs = append(exprs, expr)
|
||||
}
|
||||
|
||||
return exprs, diags
|
||||
}
|
||||
|
||||
func (p *templateParser) parseExpr() (Expression, hcl.Diagnostics) {
|
||||
next := p.Peek()
|
||||
switch tok := next.(type) {
|
||||
|
||||
case *templateLiteralToken:
|
||||
p.Read() // eat literal
|
||||
return &LiteralValueExpr{
|
||||
Val: cty.StringVal(tok.Val),
|
||||
SrcRange: tok.SrcRange,
|
||||
}, nil
|
||||
|
||||
case *templateInterpToken:
|
||||
p.Read() // eat interp
|
||||
return tok.Expr, nil
|
||||
|
||||
case *templateIfToken:
|
||||
return p.parseIf()
|
||||
|
||||
case *templateForToken:
|
||||
return p.parseFor()
|
||||
|
||||
case *templateEndToken:
|
||||
p.Read() // eat erroneous token
|
||||
return errPlaceholderExpr(tok.SrcRange), hcl.Diagnostics{
|
||||
{
|
||||
// This is a particularly unhelpful diagnostic, so callers
|
||||
// should attempt to pre-empt it and produce a more helpful
|
||||
// diagnostic that is context-aware.
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unexpected end of template",
|
||||
Detail: "The control directives within this template are unbalanced.",
|
||||
Subject: &tok.SrcRange,
|
||||
},
|
||||
}
|
||||
|
||||
case *templateEndCtrlToken:
|
||||
p.Read() // eat erroneous token
|
||||
return errPlaceholderExpr(tok.SrcRange), hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Unexpected %s directive", tok.Name()),
|
||||
Detail: "The control directives within this template are unbalanced.",
|
||||
Subject: &tok.SrcRange,
|
||||
},
|
||||
}
|
||||
|
||||
default:
|
||||
// should never happen, because above should be exhaustive
|
||||
panic(fmt.Sprintf("unhandled template token type %T", next))
|
||||
}
|
||||
}
|
||||
|
||||
func (p *templateParser) parseIf() (Expression, hcl.Diagnostics) {
|
||||
open := p.Read()
|
||||
openIf, isIf := open.(*templateIfToken)
|
||||
if !isIf {
|
||||
// should never happen if caller is behaving
|
||||
panic("parseIf called with peeker not pointing at if token")
|
||||
}
|
||||
|
||||
var ifExprs, elseExprs []Expression
|
||||
var diags hcl.Diagnostics
|
||||
var endifRange hcl.Range
|
||||
|
||||
currentExprs := &ifExprs
|
||||
Token:
|
||||
for {
|
||||
next := p.Peek()
|
||||
if end, isEnd := next.(*templateEndToken); isEnd {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unexpected end of template",
|
||||
Detail: fmt.Sprintf(
|
||||
"The if directive at %s is missing its corresponding endif directive.",
|
||||
openIf.SrcRange,
|
||||
),
|
||||
Subject: &end.SrcRange,
|
||||
})
|
||||
return errPlaceholderExpr(end.SrcRange), diags
|
||||
}
|
||||
if end, isCtrlEnd := next.(*templateEndCtrlToken); isCtrlEnd {
|
||||
p.Read() // eat end directive
|
||||
|
||||
switch end.Type {
|
||||
|
||||
case templateElse:
|
||||
if currentExprs == &ifExprs {
|
||||
currentExprs = &elseExprs
|
||||
continue Token
|
||||
}
|
||||
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unexpected else directive",
|
||||
Detail: fmt.Sprintf(
|
||||
"Already in the else clause for the if started at %s.",
|
||||
openIf.SrcRange,
|
||||
),
|
||||
Subject: &end.SrcRange,
|
||||
})
|
||||
|
||||
case templateEndIf:
|
||||
endifRange = end.SrcRange
|
||||
break Token
|
||||
|
||||
default:
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Unexpected %s directive", end.Name()),
|
||||
Detail: fmt.Sprintf(
|
||||
"Expecting an endif directive for the if started at %s.",
|
||||
openIf.SrcRange,
|
||||
),
|
||||
Subject: &end.SrcRange,
|
||||
})
|
||||
}
|
||||
|
||||
return errPlaceholderExpr(end.SrcRange), diags
|
||||
}
|
||||
|
||||
expr, exprDiags := p.parseExpr()
|
||||
diags = append(diags, exprDiags...)
|
||||
*currentExprs = append(*currentExprs, expr)
|
||||
}
|
||||
|
||||
if len(ifExprs) == 0 {
|
||||
ifExprs = append(ifExprs, &LiteralValueExpr{
|
||||
Val: cty.StringVal(""),
|
||||
SrcRange: hcl.Range{
|
||||
Filename: openIf.SrcRange.Filename,
|
||||
Start: openIf.SrcRange.End,
|
||||
End: openIf.SrcRange.End,
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(elseExprs) == 0 {
|
||||
elseExprs = append(elseExprs, &LiteralValueExpr{
|
||||
Val: cty.StringVal(""),
|
||||
SrcRange: hcl.Range{
|
||||
Filename: endifRange.Filename,
|
||||
Start: endifRange.Start,
|
||||
End: endifRange.Start,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
trueExpr := &TemplateExpr{
|
||||
Parts: ifExprs,
|
||||
SrcRange: hcl.RangeBetween(ifExprs[0].Range(), ifExprs[len(ifExprs)-1].Range()),
|
||||
}
|
||||
falseExpr := &TemplateExpr{
|
||||
Parts: elseExprs,
|
||||
SrcRange: hcl.RangeBetween(elseExprs[0].Range(), elseExprs[len(elseExprs)-1].Range()),
|
||||
}
|
||||
|
||||
return &ConditionalExpr{
|
||||
Condition: openIf.CondExpr,
|
||||
TrueResult: trueExpr,
|
||||
FalseResult: falseExpr,
|
||||
|
||||
SrcRange: hcl.RangeBetween(openIf.SrcRange, endifRange),
|
||||
}, diags
|
||||
}
|
||||
|
||||
func (p *templateParser) parseFor() (Expression, hcl.Diagnostics) {
|
||||
open := p.Read()
|
||||
openFor, isFor := open.(*templateForToken)
|
||||
if !isFor {
|
||||
// should never happen if caller is behaving
|
||||
panic("parseFor called with peeker not pointing at for token")
|
||||
}
|
||||
|
||||
var contentExprs []Expression
|
||||
var diags hcl.Diagnostics
|
||||
var endforRange hcl.Range
|
||||
|
||||
Token:
|
||||
for {
|
||||
next := p.Peek()
|
||||
if end, isEnd := next.(*templateEndToken); isEnd {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unexpected end of template",
|
||||
Detail: fmt.Sprintf(
|
||||
"The for directive at %s is missing its corresponding endfor directive.",
|
||||
openFor.SrcRange,
|
||||
),
|
||||
Subject: &end.SrcRange,
|
||||
})
|
||||
return errPlaceholderExpr(end.SrcRange), diags
|
||||
}
|
||||
if end, isCtrlEnd := next.(*templateEndCtrlToken); isCtrlEnd {
|
||||
p.Read() // eat end directive
|
||||
|
||||
switch end.Type {
|
||||
|
||||
case templateElse:
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unexpected else directive",
|
||||
Detail: "An else clause is not expected for a for directive.",
|
||||
Subject: &end.SrcRange,
|
||||
})
|
||||
|
||||
case templateEndFor:
|
||||
endforRange = end.SrcRange
|
||||
break Token
|
||||
|
||||
default:
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Unexpected %s directive", end.Name()),
|
||||
Detail: fmt.Sprintf(
|
||||
"Expecting an endfor directive corresponding to the for directive at %s.",
|
||||
openFor.SrcRange,
|
||||
),
|
||||
Subject: &end.SrcRange,
|
||||
})
|
||||
}
|
||||
|
||||
return errPlaceholderExpr(end.SrcRange), diags
|
||||
}
|
||||
|
||||
expr, exprDiags := p.parseExpr()
|
||||
diags = append(diags, exprDiags...)
|
||||
contentExprs = append(contentExprs, expr)
|
||||
}
|
||||
|
||||
if len(contentExprs) == 0 {
|
||||
contentExprs = append(contentExprs, &LiteralValueExpr{
|
||||
Val: cty.StringVal(""),
|
||||
SrcRange: hcl.Range{
|
||||
Filename: openFor.SrcRange.Filename,
|
||||
Start: openFor.SrcRange.End,
|
||||
End: openFor.SrcRange.End,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
contentExpr := &TemplateExpr{
|
||||
Parts: contentExprs,
|
||||
SrcRange: hcl.RangeBetween(contentExprs[0].Range(), contentExprs[len(contentExprs)-1].Range()),
|
||||
}
|
||||
|
||||
forExpr := &ForExpr{
|
||||
KeyVar: openFor.KeyVar,
|
||||
ValVar: openFor.ValVar,
|
||||
|
||||
CollExpr: openFor.CollExpr,
|
||||
ValExpr: contentExpr,
|
||||
|
||||
SrcRange: hcl.RangeBetween(openFor.SrcRange, endforRange),
|
||||
OpenRange: openFor.SrcRange,
|
||||
CloseRange: endforRange,
|
||||
}
|
||||
|
||||
return &TemplateJoinExpr{
|
||||
Tuple: forExpr,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func (p *templateParser) Peek() templateToken {
|
||||
return p.Tokens[p.pos]
|
||||
}
|
||||
|
||||
func (p *templateParser) Read() templateToken {
|
||||
ret := p.Peek()
|
||||
if _, end := ret.(*templateEndToken); !end {
|
||||
p.pos++
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// parseTemplateParts produces a flat sequence of "template tokens", which are
|
||||
// either literal values (with any "trimming" already applied), interpolation
|
||||
// sequences, or control flow markers.
|
||||
//
|
||||
// A further pass is required on the result to turn it into an AST.
|
||||
func (p *parser) parseTemplateParts(end TokenType) (*templateParts, hcl.Diagnostics) {
|
||||
var parts []templateToken
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
startRange := p.NextRange()
|
||||
ltrimNext := false
|
||||
nextCanTrimPrev := false
|
||||
var endRange hcl.Range
|
||||
|
||||
Token:
|
||||
for {
|
||||
next := p.Read()
|
||||
if next.Type == end {
|
||||
// all done!
|
||||
endRange = next.Range
|
||||
break
|
||||
}
|
||||
|
||||
ltrim := ltrimNext
|
||||
ltrimNext = false
|
||||
canTrimPrev := nextCanTrimPrev
|
||||
nextCanTrimPrev = false
|
||||
|
||||
switch next.Type {
|
||||
case TokenStringLit, TokenQuotedLit:
|
||||
str, strDiags := ParseStringLiteralToken(next)
|
||||
diags = append(diags, strDiags...)
|
||||
|
||||
if ltrim {
|
||||
str = strings.TrimLeftFunc(str, unicode.IsSpace)
|
||||
}
|
||||
|
||||
parts = append(parts, &templateLiteralToken{
|
||||
Val: str,
|
||||
SrcRange: next.Range,
|
||||
})
|
||||
nextCanTrimPrev = true
|
||||
|
||||
case TokenTemplateInterp:
|
||||
// if the opener is ${~ then we want to eat any trailing whitespace
|
||||
// in the preceding literal token, assuming it is indeed a literal
|
||||
// token.
|
||||
if canTrimPrev && len(next.Bytes) == 3 && next.Bytes[2] == '~' && len(parts) > 0 {
|
||||
prevExpr := parts[len(parts)-1]
|
||||
if lexpr, ok := prevExpr.(*templateLiteralToken); ok {
|
||||
lexpr.Val = strings.TrimRightFunc(lexpr.Val, unicode.IsSpace)
|
||||
}
|
||||
}
|
||||
|
||||
p.PushIncludeNewlines(false)
|
||||
expr, exprDiags := p.ParseExpression()
|
||||
diags = append(diags, exprDiags...)
|
||||
close := p.Peek()
|
||||
if close.Type != TokenTemplateSeqEnd {
|
||||
if !p.recovery {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Extra characters after interpolation expression",
|
||||
Detail: "Expected a closing brace to end the interpolation expression, but found extra characters.",
|
||||
Subject: &close.Range,
|
||||
Context: hcl.RangeBetween(startRange, close.Range).Ptr(),
|
||||
})
|
||||
}
|
||||
p.recover(TokenTemplateSeqEnd)
|
||||
} else {
|
||||
p.Read() // eat closing brace
|
||||
|
||||
// If the closer is ~} then we want to eat any leading
|
||||
// whitespace on the next token, if it turns out to be a
|
||||
// literal token.
|
||||
if len(close.Bytes) == 2 && close.Bytes[0] == '~' {
|
||||
ltrimNext = true
|
||||
}
|
||||
}
|
||||
p.PopIncludeNewlines()
|
||||
parts = append(parts, &templateInterpToken{
|
||||
Expr: expr,
|
||||
SrcRange: hcl.RangeBetween(next.Range, close.Range),
|
||||
})
|
||||
|
||||
case TokenTemplateControl:
|
||||
// if the opener is %{~ then we want to eat any trailing whitespace
|
||||
// in the preceding literal token, assuming it is indeed a literal
|
||||
// token.
|
||||
if canTrimPrev && len(next.Bytes) == 3 && next.Bytes[2] == '~' && len(parts) > 0 {
|
||||
prevExpr := parts[len(parts)-1]
|
||||
if lexpr, ok := prevExpr.(*templateLiteralToken); ok {
|
||||
lexpr.Val = strings.TrimRightFunc(lexpr.Val, unicode.IsSpace)
|
||||
}
|
||||
}
|
||||
p.PushIncludeNewlines(false)
|
||||
|
||||
kw := p.Peek()
|
||||
if kw.Type != TokenIdent {
|
||||
if !p.recovery {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid template directive",
|
||||
Detail: "A template directive keyword (\"if\", \"for\", etc) is expected at the beginning of a %{ sequence.",
|
||||
Subject: &kw.Range,
|
||||
Context: hcl.RangeBetween(next.Range, kw.Range).Ptr(),
|
||||
})
|
||||
}
|
||||
p.recover(TokenTemplateSeqEnd)
|
||||
p.PopIncludeNewlines()
|
||||
continue Token
|
||||
}
|
||||
p.Read() // eat keyword token
|
||||
|
||||
switch {
|
||||
|
||||
case ifKeyword.TokenMatches(kw):
|
||||
condExpr, exprDiags := p.ParseExpression()
|
||||
diags = append(diags, exprDiags...)
|
||||
parts = append(parts, &templateIfToken{
|
||||
CondExpr: condExpr,
|
||||
SrcRange: hcl.RangeBetween(next.Range, p.NextRange()),
|
||||
})
|
||||
|
||||
case elseKeyword.TokenMatches(kw):
|
||||
parts = append(parts, &templateEndCtrlToken{
|
||||
Type: templateElse,
|
||||
SrcRange: hcl.RangeBetween(next.Range, p.NextRange()),
|
||||
})
|
||||
|
||||
case endifKeyword.TokenMatches(kw):
|
||||
parts = append(parts, &templateEndCtrlToken{
|
||||
Type: templateEndIf,
|
||||
SrcRange: hcl.RangeBetween(next.Range, p.NextRange()),
|
||||
})
|
||||
|
||||
case forKeyword.TokenMatches(kw):
|
||||
var keyName, valName string
|
||||
if p.Peek().Type != TokenIdent {
|
||||
if !p.recovery {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'for' directive",
|
||||
Detail: "For directive requires variable name after 'for'.",
|
||||
Subject: p.Peek().Range.Ptr(),
|
||||
})
|
||||
}
|
||||
p.recover(TokenTemplateSeqEnd)
|
||||
p.PopIncludeNewlines()
|
||||
continue Token
|
||||
}
|
||||
|
||||
valName = string(p.Read().Bytes)
|
||||
|
||||
if p.Peek().Type == TokenComma {
|
||||
// What we just read was actually the key, then.
|
||||
keyName = valName
|
||||
p.Read() // eat comma
|
||||
|
||||
if p.Peek().Type != TokenIdent {
|
||||
if !p.recovery {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'for' directive",
|
||||
Detail: "For directive requires value variable name after comma.",
|
||||
Subject: p.Peek().Range.Ptr(),
|
||||
})
|
||||
}
|
||||
p.recover(TokenTemplateSeqEnd)
|
||||
p.PopIncludeNewlines()
|
||||
continue Token
|
||||
}
|
||||
|
||||
valName = string(p.Read().Bytes)
|
||||
}
|
||||
|
||||
if !inKeyword.TokenMatches(p.Peek()) {
|
||||
if !p.recovery {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid 'for' directive",
|
||||
Detail: "For directive requires 'in' keyword after names.",
|
||||
Subject: p.Peek().Range.Ptr(),
|
||||
})
|
||||
}
|
||||
p.recover(TokenTemplateSeqEnd)
|
||||
p.PopIncludeNewlines()
|
||||
continue Token
|
||||
}
|
||||
p.Read() // eat 'in' keyword
|
||||
|
||||
collExpr, collDiags := p.ParseExpression()
|
||||
diags = append(diags, collDiags...)
|
||||
parts = append(parts, &templateForToken{
|
||||
KeyVar: keyName,
|
||||
ValVar: valName,
|
||||
CollExpr: collExpr,
|
||||
|
||||
SrcRange: hcl.RangeBetween(next.Range, p.NextRange()),
|
||||
})
|
||||
|
||||
case endforKeyword.TokenMatches(kw):
|
||||
parts = append(parts, &templateEndCtrlToken{
|
||||
Type: templateEndFor,
|
||||
SrcRange: hcl.RangeBetween(next.Range, p.NextRange()),
|
||||
})
|
||||
|
||||
default:
|
||||
if !p.recovery {
|
||||
suggestions := []string{"if", "for", "else", "endif", "endfor"}
|
||||
given := string(kw.Bytes)
|
||||
suggestion := nameSuggestion(given, suggestions)
|
||||
if suggestion != "" {
|
||||
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
|
||||
}
|
||||
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid template control keyword",
|
||||
Detail: fmt.Sprintf("%q is not a valid template control keyword.%s", given, suggestion),
|
||||
Subject: &kw.Range,
|
||||
Context: hcl.RangeBetween(next.Range, kw.Range).Ptr(),
|
||||
})
|
||||
}
|
||||
p.recover(TokenTemplateSeqEnd)
|
||||
p.PopIncludeNewlines()
|
||||
continue Token
|
||||
|
||||
}
|
||||
|
||||
close := p.Peek()
|
||||
if close.Type != TokenTemplateSeqEnd {
|
||||
if !p.recovery {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Extra characters in %s marker", kw.Bytes),
|
||||
Detail: "Expected a closing brace to end the sequence, but found extra characters.",
|
||||
Subject: &close.Range,
|
||||
Context: hcl.RangeBetween(startRange, close.Range).Ptr(),
|
||||
})
|
||||
}
|
||||
p.recover(TokenTemplateSeqEnd)
|
||||
} else {
|
||||
p.Read() // eat closing brace
|
||||
|
||||
// If the closer is ~} then we want to eat any leading
|
||||
// whitespace on the next token, if it turns out to be a
|
||||
// literal token.
|
||||
if len(close.Bytes) == 2 && close.Bytes[0] == '~' {
|
||||
ltrimNext = true
|
||||
}
|
||||
}
|
||||
p.PopIncludeNewlines()
|
||||
|
||||
default:
|
||||
if !p.recovery {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unterminated template string",
|
||||
Detail: "No closing marker was found for the string.",
|
||||
Subject: &next.Range,
|
||||
Context: hcl.RangeBetween(startRange, next.Range).Ptr(),
|
||||
})
|
||||
}
|
||||
final := p.recover(end)
|
||||
endRange = final.Range
|
||||
break Token
|
||||
}
|
||||
}
|
||||
|
||||
if len(parts) == 0 {
|
||||
// If a sequence has no content, we'll treat it as if it had an
|
||||
// empty string in it because that's what the user probably means
|
||||
// if they write "" in configuration.
|
||||
parts = append(parts, &templateLiteralToken{
|
||||
Val: "",
|
||||
SrcRange: hcl.Range{
|
||||
// Range is the zero-character span immediately after the
|
||||
// opening quote.
|
||||
Filename: startRange.Filename,
|
||||
Start: startRange.End,
|
||||
End: startRange.End,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Always end with an end token, so the parser can produce diagnostics
|
||||
// about unclosed items with proper position information.
|
||||
parts = append(parts, &templateEndToken{
|
||||
SrcRange: endRange,
|
||||
})
|
||||
|
||||
ret := &templateParts{
|
||||
Tokens: parts,
|
||||
SrcRange: hcl.RangeBetween(startRange, endRange),
|
||||
}
|
||||
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
// flushHeredocTemplateParts modifies in-place the line-leading literal strings
|
||||
// to apply the flush heredoc processing rule: find the line with the smallest
|
||||
// number of whitespace characters as prefix and then trim that number of
|
||||
// characters from all of the lines.
|
||||
//
|
||||
// This rule is applied to static tokens rather than to the rendered result,
|
||||
// so interpolating a string with leading whitespace cannot affect the chosen
|
||||
// prefix length.
|
||||
func flushHeredocTemplateParts(parts *templateParts) {
|
||||
if len(parts.Tokens) == 0 {
|
||||
// Nothing to do
|
||||
return
|
||||
}
|
||||
|
||||
const maxInt = int((^uint(0)) >> 1)
|
||||
|
||||
minSpaces := maxInt
|
||||
newline := true
|
||||
var adjust []*templateLiteralToken
|
||||
for _, ttok := range parts.Tokens {
|
||||
if newline {
|
||||
newline = false
|
||||
var spaces int
|
||||
if lit, ok := ttok.(*templateLiteralToken); ok {
|
||||
orig := lit.Val
|
||||
trimmed := strings.TrimLeftFunc(orig, unicode.IsSpace)
|
||||
// If a token is entirely spaces and ends with a newline
|
||||
// then it's a "blank line" and thus not considered for
|
||||
// space-prefix-counting purposes.
|
||||
if len(trimmed) == 0 && strings.HasSuffix(orig, "\n") {
|
||||
spaces = maxInt
|
||||
} else {
|
||||
spaceBytes := len(lit.Val) - len(trimmed)
|
||||
spaces, _ = textseg.TokenCount([]byte(orig[:spaceBytes]), textseg.ScanGraphemeClusters)
|
||||
adjust = append(adjust, lit)
|
||||
}
|
||||
} else if _, ok := ttok.(*templateEndToken); ok {
|
||||
break // don't process the end token since it never has spaces before it
|
||||
}
|
||||
if spaces < minSpaces {
|
||||
minSpaces = spaces
|
||||
}
|
||||
}
|
||||
if lit, ok := ttok.(*templateLiteralToken); ok {
|
||||
if strings.HasSuffix(lit.Val, "\n") {
|
||||
newline = true // The following token, if any, begins a new line
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, lit := range adjust {
|
||||
// Since we want to count space _characters_ rather than space _bytes_,
|
||||
// we can't just do a straightforward slice operation here and instead
|
||||
// need to hunt for the split point with a scanner.
|
||||
valBytes := []byte(lit.Val)
|
||||
spaceByteCount := 0
|
||||
for i := 0; i < minSpaces; i++ {
|
||||
adv, _, _ := textseg.ScanGraphemeClusters(valBytes, true)
|
||||
spaceByteCount += adv
|
||||
valBytes = valBytes[adv:]
|
||||
}
|
||||
lit.Val = lit.Val[spaceByteCount:]
|
||||
lit.SrcRange.Start.Column += minSpaces
|
||||
lit.SrcRange.Start.Byte += spaceByteCount
|
||||
}
|
||||
}
|
||||
|
||||
type templateParts struct {
|
||||
Tokens []templateToken
|
||||
SrcRange hcl.Range
|
||||
}
|
||||
|
||||
// templateToken is a higher-level token that represents a single atom within
|
||||
// the template language. Our template parsing first raises the raw token
|
||||
// stream to a sequence of templateToken, and then transforms the result into
|
||||
// an expression tree.
|
||||
type templateToken interface {
|
||||
templateToken() templateToken
|
||||
}
|
||||
|
||||
type templateLiteralToken struct {
|
||||
Val string
|
||||
SrcRange hcl.Range
|
||||
isTemplateToken
|
||||
}
|
||||
|
||||
type templateInterpToken struct {
|
||||
Expr Expression
|
||||
SrcRange hcl.Range
|
||||
isTemplateToken
|
||||
}
|
||||
|
||||
type templateIfToken struct {
|
||||
CondExpr Expression
|
||||
SrcRange hcl.Range
|
||||
isTemplateToken
|
||||
}
|
||||
|
||||
type templateForToken struct {
|
||||
KeyVar string // empty if ignoring key
|
||||
ValVar string
|
||||
CollExpr Expression
|
||||
SrcRange hcl.Range
|
||||
isTemplateToken
|
||||
}
|
||||
|
||||
type templateEndCtrlType int
|
||||
|
||||
const (
|
||||
templateEndIf templateEndCtrlType = iota
|
||||
templateElse
|
||||
templateEndFor
|
||||
)
|
||||
|
||||
type templateEndCtrlToken struct {
|
||||
Type templateEndCtrlType
|
||||
SrcRange hcl.Range
|
||||
isTemplateToken
|
||||
}
|
||||
|
||||
func (t *templateEndCtrlToken) Name() string {
|
||||
switch t.Type {
|
||||
case templateEndIf:
|
||||
return "endif"
|
||||
case templateElse:
|
||||
return "else"
|
||||
case templateEndFor:
|
||||
return "endfor"
|
||||
default:
|
||||
// should never happen
|
||||
panic("invalid templateEndCtrlType")
|
||||
}
|
||||
}
|
||||
|
||||
type templateEndToken struct {
|
||||
SrcRange hcl.Range
|
||||
isTemplateToken
|
||||
}
|
||||
|
||||
type isTemplateToken [0]int
|
||||
|
||||
func (t isTemplateToken) templateToken() templateToken {
|
||||
return t
|
||||
}
|
159
vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser_traversal.go
generated
vendored
Normal file
159
vendor/github.com/hashicorp/hcl/v2/hclsyntax/parser_traversal.go
generated
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// ParseTraversalAbs parses an absolute traversal that is assumed to consume
|
||||
// all of the remaining tokens in the peeker. The usual parser recovery
|
||||
// behavior is not supported here because traversals are not expected to
|
||||
// be parsed as part of a larger program.
|
||||
func (p *parser) ParseTraversalAbs() (hcl.Traversal, hcl.Diagnostics) {
|
||||
var ret hcl.Traversal
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
// Absolute traversal must always begin with a variable name
|
||||
varTok := p.Read()
|
||||
if varTok.Type != TokenIdent {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Variable name required",
|
||||
Detail: "Must begin with a variable name.",
|
||||
Subject: &varTok.Range,
|
||||
})
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
varName := string(varTok.Bytes)
|
||||
ret = append(ret, hcl.TraverseRoot{
|
||||
Name: varName,
|
||||
SrcRange: varTok.Range,
|
||||
})
|
||||
|
||||
for {
|
||||
next := p.Peek()
|
||||
|
||||
if next.Type == TokenEOF {
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
switch next.Type {
|
||||
case TokenDot:
|
||||
// Attribute access
|
||||
dot := p.Read() // eat dot
|
||||
nameTok := p.Read()
|
||||
if nameTok.Type != TokenIdent {
|
||||
if nameTok.Type == TokenStar {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Attribute name required",
|
||||
Detail: "Splat expressions (.*) may not be used here.",
|
||||
Subject: &nameTok.Range,
|
||||
Context: hcl.RangeBetween(varTok.Range, nameTok.Range).Ptr(),
|
||||
})
|
||||
} else {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Attribute name required",
|
||||
Detail: "Dot must be followed by attribute name.",
|
||||
Subject: &nameTok.Range,
|
||||
Context: hcl.RangeBetween(varTok.Range, nameTok.Range).Ptr(),
|
||||
})
|
||||
}
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
attrName := string(nameTok.Bytes)
|
||||
ret = append(ret, hcl.TraverseAttr{
|
||||
Name: attrName,
|
||||
SrcRange: hcl.RangeBetween(dot.Range, nameTok.Range),
|
||||
})
|
||||
case TokenOBrack:
|
||||
// Index
|
||||
open := p.Read() // eat open bracket
|
||||
next := p.Peek()
|
||||
|
||||
switch next.Type {
|
||||
case TokenNumberLit:
|
||||
tok := p.Read() // eat number
|
||||
numVal, numDiags := p.numberLitValue(tok)
|
||||
diags = append(diags, numDiags...)
|
||||
|
||||
close := p.Read()
|
||||
if close.Type != TokenCBrack {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unclosed index brackets",
|
||||
Detail: "Index key must be followed by a closing bracket.",
|
||||
Subject: &close.Range,
|
||||
Context: hcl.RangeBetween(open.Range, close.Range).Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
ret = append(ret, hcl.TraverseIndex{
|
||||
Key: numVal,
|
||||
SrcRange: hcl.RangeBetween(open.Range, close.Range),
|
||||
})
|
||||
|
||||
if diags.HasErrors() {
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
case TokenOQuote:
|
||||
str, _, strDiags := p.parseQuotedStringLiteral()
|
||||
diags = append(diags, strDiags...)
|
||||
|
||||
close := p.Read()
|
||||
if close.Type != TokenCBrack {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unclosed index brackets",
|
||||
Detail: "Index key must be followed by a closing bracket.",
|
||||
Subject: &close.Range,
|
||||
Context: hcl.RangeBetween(open.Range, close.Range).Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
ret = append(ret, hcl.TraverseIndex{
|
||||
Key: cty.StringVal(str),
|
||||
SrcRange: hcl.RangeBetween(open.Range, close.Range),
|
||||
})
|
||||
|
||||
if diags.HasErrors() {
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
default:
|
||||
if next.Type == TokenStar {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Attribute name required",
|
||||
Detail: "Splat expressions ([*]) may not be used here.",
|
||||
Subject: &next.Range,
|
||||
Context: hcl.RangeBetween(varTok.Range, next.Range).Ptr(),
|
||||
})
|
||||
} else {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Index value required",
|
||||
Detail: "Index brackets must contain either a literal number or a literal string.",
|
||||
Subject: &next.Range,
|
||||
Context: hcl.RangeBetween(varTok.Range, next.Range).Ptr(),
|
||||
})
|
||||
}
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
default:
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid character",
|
||||
Detail: "Expected an attribute access or an index operator.",
|
||||
Subject: &next.Range,
|
||||
Context: hcl.RangeBetween(varTok.Range, next.Range).Ptr(),
|
||||
})
|
||||
return ret, diags
|
||||
}
|
||||
}
|
||||
}
|
212
vendor/github.com/hashicorp/hcl/v2/hclsyntax/peeker.go
generated
vendored
Normal file
212
vendor/github.com/hashicorp/hcl/v2/hclsyntax/peeker.go
generated
vendored
Normal file
@ -0,0 +1,212 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// This is set to true at init() time in tests, to enable more useful output
|
||||
// if a stack discipline error is detected. It should not be enabled in
|
||||
// normal mode since there is a performance penalty from accessing the
|
||||
// runtime stack to produce the traces, but could be temporarily set to
|
||||
// true for debugging if desired.
|
||||
var tracePeekerNewlinesStack = false
|
||||
|
||||
type peeker struct {
|
||||
Tokens Tokens
|
||||
NextIndex int
|
||||
|
||||
IncludeComments bool
|
||||
IncludeNewlinesStack []bool
|
||||
|
||||
// used only when tracePeekerNewlinesStack is set
|
||||
newlineStackChanges []peekerNewlineStackChange
|
||||
}
|
||||
|
||||
// for use in debugging the stack usage only
|
||||
type peekerNewlineStackChange struct {
|
||||
Pushing bool // if false, then popping
|
||||
Frame runtime.Frame
|
||||
Include bool
|
||||
}
|
||||
|
||||
func newPeeker(tokens Tokens, includeComments bool) *peeker {
|
||||
return &peeker{
|
||||
Tokens: tokens,
|
||||
IncludeComments: includeComments,
|
||||
|
||||
IncludeNewlinesStack: []bool{true},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *peeker) Peek() Token {
|
||||
ret, _ := p.nextToken()
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *peeker) Read() Token {
|
||||
ret, nextIdx := p.nextToken()
|
||||
p.NextIndex = nextIdx
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *peeker) NextRange() hcl.Range {
|
||||
return p.Peek().Range
|
||||
}
|
||||
|
||||
func (p *peeker) PrevRange() hcl.Range {
|
||||
if p.NextIndex == 0 {
|
||||
return p.NextRange()
|
||||
}
|
||||
|
||||
return p.Tokens[p.NextIndex-1].Range
|
||||
}
|
||||
|
||||
func (p *peeker) nextToken() (Token, int) {
|
||||
for i := p.NextIndex; i < len(p.Tokens); i++ {
|
||||
tok := p.Tokens[i]
|
||||
switch tok.Type {
|
||||
case TokenComment:
|
||||
if !p.IncludeComments {
|
||||
// Single-line comment tokens, starting with # or //, absorb
|
||||
// the trailing newline that terminates them as part of their
|
||||
// bytes. When we're filtering out comments, we must as a
|
||||
// special case transform these to newline tokens in order
|
||||
// to properly parse newline-terminated block items.
|
||||
|
||||
if p.includingNewlines() {
|
||||
if len(tok.Bytes) > 0 && tok.Bytes[len(tok.Bytes)-1] == '\n' {
|
||||
fakeNewline := Token{
|
||||
Type: TokenNewline,
|
||||
Bytes: tok.Bytes[len(tok.Bytes)-1 : len(tok.Bytes)],
|
||||
|
||||
// We use the whole token range as the newline
|
||||
// range, even though that's a little... weird,
|
||||
// because otherwise we'd need to go count
|
||||
// characters again in order to figure out the
|
||||
// column of the newline, and that complexity
|
||||
// isn't justified when ranges of newlines are
|
||||
// so rarely printed anyway.
|
||||
Range: tok.Range,
|
||||
}
|
||||
return fakeNewline, i + 1
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
case TokenNewline:
|
||||
if !p.includingNewlines() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return tok, i + 1
|
||||
}
|
||||
|
||||
// if we fall out here then we'll return the EOF token, and leave
|
||||
// our index pointed off the end of the array so we'll keep
|
||||
// returning EOF in future too.
|
||||
return p.Tokens[len(p.Tokens)-1], len(p.Tokens)
|
||||
}
|
||||
|
||||
func (p *peeker) includingNewlines() bool {
|
||||
return p.IncludeNewlinesStack[len(p.IncludeNewlinesStack)-1]
|
||||
}
|
||||
|
||||
func (p *peeker) PushIncludeNewlines(include bool) {
|
||||
if tracePeekerNewlinesStack {
|
||||
// Record who called us so that we can more easily track down any
|
||||
// mismanagement of the stack in the parser.
|
||||
callers := []uintptr{0}
|
||||
runtime.Callers(2, callers)
|
||||
frames := runtime.CallersFrames(callers)
|
||||
frame, _ := frames.Next()
|
||||
p.newlineStackChanges = append(p.newlineStackChanges, peekerNewlineStackChange{
|
||||
true, frame, include,
|
||||
})
|
||||
}
|
||||
|
||||
p.IncludeNewlinesStack = append(p.IncludeNewlinesStack, include)
|
||||
}
|
||||
|
||||
func (p *peeker) PopIncludeNewlines() bool {
|
||||
stack := p.IncludeNewlinesStack
|
||||
remain, ret := stack[:len(stack)-1], stack[len(stack)-1]
|
||||
p.IncludeNewlinesStack = remain
|
||||
|
||||
if tracePeekerNewlinesStack {
|
||||
// Record who called us so that we can more easily track down any
|
||||
// mismanagement of the stack in the parser.
|
||||
callers := []uintptr{0}
|
||||
runtime.Callers(2, callers)
|
||||
frames := runtime.CallersFrames(callers)
|
||||
frame, _ := frames.Next()
|
||||
p.newlineStackChanges = append(p.newlineStackChanges, peekerNewlineStackChange{
|
||||
false, frame, ret,
|
||||
})
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// AssertEmptyNewlinesStack checks if the IncludeNewlinesStack is empty, doing
|
||||
// panicking if it is not. This can be used to catch stack mismanagement that
|
||||
// might otherwise just cause confusing downstream errors.
|
||||
//
|
||||
// This function is a no-op if the stack is empty when called.
|
||||
//
|
||||
// If newlines stack tracing is enabled by setting the global variable
|
||||
// tracePeekerNewlinesStack at init time, a full log of all of the push/pop
|
||||
// calls will be produced to help identify which caller in the parser is
|
||||
// misbehaving.
|
||||
func (p *peeker) AssertEmptyIncludeNewlinesStack() {
|
||||
if len(p.IncludeNewlinesStack) != 1 {
|
||||
// Should never happen; indicates mismanagement of the stack inside
|
||||
// the parser.
|
||||
if p.newlineStackChanges != nil { // only if traceNewlinesStack is enabled above
|
||||
panic(fmt.Errorf(
|
||||
"non-empty IncludeNewlinesStack after parse with %d calls unaccounted for:\n%s",
|
||||
len(p.IncludeNewlinesStack)-1,
|
||||
formatPeekerNewlineStackChanges(p.newlineStackChanges),
|
||||
))
|
||||
} else {
|
||||
panic(fmt.Errorf("non-empty IncludeNewlinesStack after parse: %#v", p.IncludeNewlinesStack))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatPeekerNewlineStackChanges(changes []peekerNewlineStackChange) string {
|
||||
indent := 0
|
||||
var buf bytes.Buffer
|
||||
for _, change := range changes {
|
||||
funcName := change.Frame.Function
|
||||
if idx := strings.LastIndexByte(funcName, '.'); idx != -1 {
|
||||
funcName = funcName[idx+1:]
|
||||
}
|
||||
filename := change.Frame.File
|
||||
if idx := strings.LastIndexByte(filename, filepath.Separator); idx != -1 {
|
||||
filename = filename[idx+1:]
|
||||
}
|
||||
|
||||
switch change.Pushing {
|
||||
|
||||
case true:
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(&buf, "PUSH %#v (%s at %s:%d)\n", change.Include, funcName, filename, change.Frame.Line)
|
||||
indent++
|
||||
|
||||
case false:
|
||||
indent--
|
||||
buf.WriteString(strings.Repeat(" ", indent))
|
||||
fmt.Fprintf(&buf, "POP %#v (%s at %s:%d)\n", change.Include, funcName, filename, change.Frame.Line)
|
||||
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
171
vendor/github.com/hashicorp/hcl/v2/hclsyntax/public.go
generated
vendored
Normal file
171
vendor/github.com/hashicorp/hcl/v2/hclsyntax/public.go
generated
vendored
Normal file
@ -0,0 +1,171 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// ParseConfig parses the given buffer as a whole HCL config file, returning
|
||||
// a *hcl.File representing its contents. If HasErrors called on the returned
|
||||
// diagnostics returns true, the returned body is likely to be incomplete
|
||||
// and should therefore be used with care.
|
||||
//
|
||||
// The body in the returned file has dynamic type *hclsyntax.Body, so callers
|
||||
// may freely type-assert this to get access to the full hclsyntax API in
|
||||
// situations where detailed access is required. However, most common use-cases
|
||||
// should be served using the hcl.Body interface to ensure compatibility with
|
||||
// other configurationg syntaxes, such as JSON.
|
||||
func ParseConfig(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Diagnostics) {
|
||||
tokens, diags := LexConfig(src, filename, start)
|
||||
peeker := newPeeker(tokens, false)
|
||||
parser := &parser{peeker: peeker}
|
||||
body, parseDiags := parser.ParseBody(TokenEOF)
|
||||
diags = append(diags, parseDiags...)
|
||||
|
||||
// Panic if the parser uses incorrect stack discipline with the peeker's
|
||||
// newlines stack, since otherwise it will produce confusing downstream
|
||||
// errors.
|
||||
peeker.AssertEmptyIncludeNewlinesStack()
|
||||
|
||||
return &hcl.File{
|
||||
Body: body,
|
||||
Bytes: src,
|
||||
|
||||
Nav: navigation{
|
||||
root: body,
|
||||
},
|
||||
}, diags
|
||||
}
|
||||
|
||||
// ParseExpression parses the given buffer as a standalone HCL expression,
|
||||
// returning it as an instance of Expression.
|
||||
func ParseExpression(src []byte, filename string, start hcl.Pos) (Expression, hcl.Diagnostics) {
|
||||
tokens, diags := LexExpression(src, filename, start)
|
||||
peeker := newPeeker(tokens, false)
|
||||
parser := &parser{peeker: peeker}
|
||||
|
||||
// Bare expressions are always parsed in "ignore newlines" mode, as if
|
||||
// they were wrapped in parentheses.
|
||||
parser.PushIncludeNewlines(false)
|
||||
|
||||
expr, parseDiags := parser.ParseExpression()
|
||||
diags = append(diags, parseDiags...)
|
||||
|
||||
next := parser.Peek()
|
||||
if next.Type != TokenEOF && !parser.recovery {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Extra characters after expression",
|
||||
Detail: "An expression was successfully parsed, but extra characters were found after it.",
|
||||
Subject: &next.Range,
|
||||
})
|
||||
}
|
||||
|
||||
parser.PopIncludeNewlines()
|
||||
|
||||
// Panic if the parser uses incorrect stack discipline with the peeker's
|
||||
// newlines stack, since otherwise it will produce confusing downstream
|
||||
// errors.
|
||||
peeker.AssertEmptyIncludeNewlinesStack()
|
||||
|
||||
return expr, diags
|
||||
}
|
||||
|
||||
// ParseTemplate parses the given buffer as a standalone HCL template,
|
||||
// returning it as an instance of Expression.
|
||||
func ParseTemplate(src []byte, filename string, start hcl.Pos) (Expression, hcl.Diagnostics) {
|
||||
tokens, diags := LexTemplate(src, filename, start)
|
||||
peeker := newPeeker(tokens, false)
|
||||
parser := &parser{peeker: peeker}
|
||||
expr, parseDiags := parser.ParseTemplate()
|
||||
diags = append(diags, parseDiags...)
|
||||
|
||||
// Panic if the parser uses incorrect stack discipline with the peeker's
|
||||
// newlines stack, since otherwise it will produce confusing downstream
|
||||
// errors.
|
||||
peeker.AssertEmptyIncludeNewlinesStack()
|
||||
|
||||
return expr, diags
|
||||
}
|
||||
|
||||
// ParseTraversalAbs parses the given buffer as a standalone absolute traversal.
|
||||
//
|
||||
// Parsing as a traversal is more limited than parsing as an expession since
|
||||
// it allows only attribute and indexing operations on variables. Traverals
|
||||
// are useful as a syntax for referring to objects without necessarily
|
||||
// evaluating them.
|
||||
func ParseTraversalAbs(src []byte, filename string, start hcl.Pos) (hcl.Traversal, hcl.Diagnostics) {
|
||||
tokens, diags := LexExpression(src, filename, start)
|
||||
peeker := newPeeker(tokens, false)
|
||||
parser := &parser{peeker: peeker}
|
||||
|
||||
// Bare traverals are always parsed in "ignore newlines" mode, as if
|
||||
// they were wrapped in parentheses.
|
||||
parser.PushIncludeNewlines(false)
|
||||
|
||||
expr, parseDiags := parser.ParseTraversalAbs()
|
||||
diags = append(diags, parseDiags...)
|
||||
|
||||
parser.PopIncludeNewlines()
|
||||
|
||||
// Panic if the parser uses incorrect stack discipline with the peeker's
|
||||
// newlines stack, since otherwise it will produce confusing downstream
|
||||
// errors.
|
||||
peeker.AssertEmptyIncludeNewlinesStack()
|
||||
|
||||
return expr, diags
|
||||
}
|
||||
|
||||
// LexConfig performs lexical analysis on the given buffer, treating it as a
|
||||
// whole HCL config file, and returns the resulting tokens.
|
||||
//
|
||||
// Only minimal validation is done during lexical analysis, so the returned
|
||||
// diagnostics may include errors about lexical issues such as bad character
|
||||
// encodings or unrecognized characters, but full parsing is required to
|
||||
// detect _all_ syntax errors.
|
||||
func LexConfig(src []byte, filename string, start hcl.Pos) (Tokens, hcl.Diagnostics) {
|
||||
tokens := scanTokens(src, filename, start, scanNormal)
|
||||
diags := checkInvalidTokens(tokens)
|
||||
return tokens, diags
|
||||
}
|
||||
|
||||
// LexExpression performs lexical analysis on the given buffer, treating it as
|
||||
// a standalone HCL expression, and returns the resulting tokens.
|
||||
//
|
||||
// Only minimal validation is done during lexical analysis, so the returned
|
||||
// diagnostics may include errors about lexical issues such as bad character
|
||||
// encodings or unrecognized characters, but full parsing is required to
|
||||
// detect _all_ syntax errors.
|
||||
func LexExpression(src []byte, filename string, start hcl.Pos) (Tokens, hcl.Diagnostics) {
|
||||
// This is actually just the same thing as LexConfig, since configs
|
||||
// and expressions lex in the same way.
|
||||
tokens := scanTokens(src, filename, start, scanNormal)
|
||||
diags := checkInvalidTokens(tokens)
|
||||
return tokens, diags
|
||||
}
|
||||
|
||||
// LexTemplate performs lexical analysis on the given buffer, treating it as a
|
||||
// standalone HCL template, and returns the resulting tokens.
|
||||
//
|
||||
// Only minimal validation is done during lexical analysis, so the returned
|
||||
// diagnostics may include errors about lexical issues such as bad character
|
||||
// encodings or unrecognized characters, but full parsing is required to
|
||||
// detect _all_ syntax errors.
|
||||
func LexTemplate(src []byte, filename string, start hcl.Pos) (Tokens, hcl.Diagnostics) {
|
||||
tokens := scanTokens(src, filename, start, scanTemplate)
|
||||
diags := checkInvalidTokens(tokens)
|
||||
return tokens, diags
|
||||
}
|
||||
|
||||
// ValidIdentifier tests if the given string could be a valid identifier in
|
||||
// a native syntax expression.
|
||||
//
|
||||
// This is useful when accepting names from the user that will be used as
|
||||
// variable or attribute names in the scope, to ensure that any name chosen
|
||||
// will be traversable using the variable or attribute traversal syntax.
|
||||
func ValidIdentifier(s string) bool {
|
||||
// This is a kinda-expensive way to do something pretty simple, but it
|
||||
// is easiest to do with our existing scanner-related infrastructure here
|
||||
// and nobody should be validating identifiers in a tight loop.
|
||||
tokens := scanTokens([]byte(s), "", hcl.Pos{}, scanIdentOnly)
|
||||
return len(tokens) == 2 && tokens[0].Type == TokenIdent && tokens[1].Type == TokenEOF
|
||||
}
|
301
vendor/github.com/hashicorp/hcl/v2/hclsyntax/scan_string_lit.go
generated
vendored
Normal file
301
vendor/github.com/hashicorp/hcl/v2/hclsyntax/scan_string_lit.go
generated
vendored
Normal file
@ -0,0 +1,301 @@
|
||||
//line scan_string_lit.rl:1
|
||||
|
||||
package hclsyntax
|
||||
|
||||
// This file is generated from scan_string_lit.rl. DO NOT EDIT.
|
||||
|
||||
//line scan_string_lit.go:9
|
||||
var _hclstrtok_actions []byte = []byte{
|
||||
0, 1, 0, 1, 1, 2, 1, 0,
|
||||
}
|
||||
|
||||
var _hclstrtok_key_offsets []byte = []byte{
|
||||
0, 0, 2, 4, 6, 10, 14, 18,
|
||||
22, 27, 31, 36, 41, 46, 51, 57,
|
||||
62, 74, 85, 96, 107, 118, 129, 140,
|
||||
151,
|
||||
}
|
||||
|
||||
var _hclstrtok_trans_keys []byte = []byte{
|
||||
128, 191, 128, 191, 128, 191, 10, 13,
|
||||
36, 37, 10, 13, 36, 37, 10, 13,
|
||||
36, 37, 10, 13, 36, 37, 10, 13,
|
||||
36, 37, 123, 10, 13, 36, 37, 10,
|
||||
13, 36, 37, 92, 10, 13, 36, 37,
|
||||
92, 10, 13, 36, 37, 92, 10, 13,
|
||||
36, 37, 92, 10, 13, 36, 37, 92,
|
||||
123, 10, 13, 36, 37, 92, 85, 117,
|
||||
128, 191, 192, 223, 224, 239, 240, 247,
|
||||
248, 255, 10, 13, 36, 37, 92, 48,
|
||||
57, 65, 70, 97, 102, 10, 13, 36,
|
||||
37, 92, 48, 57, 65, 70, 97, 102,
|
||||
10, 13, 36, 37, 92, 48, 57, 65,
|
||||
70, 97, 102, 10, 13, 36, 37, 92,
|
||||
48, 57, 65, 70, 97, 102, 10, 13,
|
||||
36, 37, 92, 48, 57, 65, 70, 97,
|
||||
102, 10, 13, 36, 37, 92, 48, 57,
|
||||
65, 70, 97, 102, 10, 13, 36, 37,
|
||||
92, 48, 57, 65, 70, 97, 102, 10,
|
||||
13, 36, 37, 92, 48, 57, 65, 70,
|
||||
97, 102,
|
||||
}
|
||||
|
||||
var _hclstrtok_single_lengths []byte = []byte{
|
||||
0, 0, 0, 0, 4, 4, 4, 4,
|
||||
5, 4, 5, 5, 5, 5, 6, 5,
|
||||
2, 5, 5, 5, 5, 5, 5, 5,
|
||||
5,
|
||||
}
|
||||
|
||||
var _hclstrtok_range_lengths []byte = []byte{
|
||||
0, 1, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
5, 3, 3, 3, 3, 3, 3, 3,
|
||||
3,
|
||||
}
|
||||
|
||||
var _hclstrtok_index_offsets []byte = []byte{
|
||||
0, 0, 2, 4, 6, 11, 16, 21,
|
||||
26, 32, 37, 43, 49, 55, 61, 68,
|
||||
74, 82, 91, 100, 109, 118, 127, 136,
|
||||
145,
|
||||
}
|
||||
|
||||
var _hclstrtok_indicies []byte = []byte{
|
||||
0, 1, 2, 1, 3, 1, 5, 6,
|
||||
7, 8, 4, 10, 11, 12, 13, 9,
|
||||
14, 11, 12, 13, 9, 10, 11, 15,
|
||||
13, 9, 10, 11, 12, 13, 14, 9,
|
||||
10, 11, 12, 15, 9, 17, 18, 19,
|
||||
20, 21, 16, 23, 24, 25, 26, 27,
|
||||
22, 0, 24, 25, 26, 27, 22, 23,
|
||||
24, 28, 26, 27, 22, 23, 24, 25,
|
||||
26, 27, 0, 22, 23, 24, 25, 28,
|
||||
27, 22, 29, 30, 22, 2, 3, 31,
|
||||
22, 0, 23, 24, 25, 26, 27, 32,
|
||||
32, 32, 22, 23, 24, 25, 26, 27,
|
||||
33, 33, 33, 22, 23, 24, 25, 26,
|
||||
27, 34, 34, 34, 22, 23, 24, 25,
|
||||
26, 27, 30, 30, 30, 22, 23, 24,
|
||||
25, 26, 27, 35, 35, 35, 22, 23,
|
||||
24, 25, 26, 27, 36, 36, 36, 22,
|
||||
23, 24, 25, 26, 27, 37, 37, 37,
|
||||
22, 23, 24, 25, 26, 27, 0, 0,
|
||||
0, 22,
|
||||
}
|
||||
|
||||
var _hclstrtok_trans_targs []byte = []byte{
|
||||
11, 0, 1, 2, 4, 5, 6, 7,
|
||||
9, 4, 5, 6, 7, 9, 5, 8,
|
||||
10, 11, 12, 13, 15, 16, 10, 11,
|
||||
12, 13, 15, 16, 14, 17, 21, 3,
|
||||
18, 19, 20, 22, 23, 24,
|
||||
}
|
||||
|
||||
var _hclstrtok_trans_actions []byte = []byte{
|
||||
0, 0, 0, 0, 0, 1, 1, 1,
|
||||
1, 3, 5, 5, 5, 5, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 3, 5,
|
||||
5, 5, 5, 5, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0,
|
||||
}
|
||||
|
||||
var _hclstrtok_eof_actions []byte = []byte{
|
||||
0, 0, 0, 0, 0, 3, 3, 3,
|
||||
3, 3, 0, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3,
|
||||
}
|
||||
|
||||
const hclstrtok_start int = 4
|
||||
const hclstrtok_first_final int = 4
|
||||
const hclstrtok_error int = 0
|
||||
|
||||
const hclstrtok_en_quoted int = 10
|
||||
const hclstrtok_en_unquoted int = 4
|
||||
|
||||
//line scan_string_lit.rl:10
|
||||
|
||||
func scanStringLit(data []byte, quoted bool) [][]byte {
|
||||
var ret [][]byte
|
||||
|
||||
//line scan_string_lit.rl:61
|
||||
|
||||
// Ragel state
|
||||
p := 0 // "Pointer" into data
|
||||
pe := len(data) // End-of-data "pointer"
|
||||
ts := 0
|
||||
te := 0
|
||||
eof := pe
|
||||
|
||||
var cs int // current state
|
||||
switch {
|
||||
case quoted:
|
||||
cs = hclstrtok_en_quoted
|
||||
default:
|
||||
cs = hclstrtok_en_unquoted
|
||||
}
|
||||
|
||||
// Make Go compiler happy
|
||||
_ = ts
|
||||
_ = eof
|
||||
|
||||
/*token := func () {
|
||||
ret = append(ret, data[ts:te])
|
||||
}*/
|
||||
|
||||
//line scan_string_lit.go:154
|
||||
{
|
||||
}
|
||||
|
||||
//line scan_string_lit.go:158
|
||||
{
|
||||
var _klen int
|
||||
var _trans int
|
||||
var _acts int
|
||||
var _nacts uint
|
||||
var _keys int
|
||||
if p == pe {
|
||||
goto _test_eof
|
||||
}
|
||||
if cs == 0 {
|
||||
goto _out
|
||||
}
|
||||
_resume:
|
||||
_keys = int(_hclstrtok_key_offsets[cs])
|
||||
_trans = int(_hclstrtok_index_offsets[cs])
|
||||
|
||||
_klen = int(_hclstrtok_single_lengths[cs])
|
||||
if _klen > 0 {
|
||||
_lower := int(_keys)
|
||||
var _mid int
|
||||
_upper := int(_keys + _klen - 1)
|
||||
for {
|
||||
if _upper < _lower {
|
||||
break
|
||||
}
|
||||
|
||||
_mid = _lower + ((_upper - _lower) >> 1)
|
||||
switch {
|
||||
case data[p] < _hclstrtok_trans_keys[_mid]:
|
||||
_upper = _mid - 1
|
||||
case data[p] > _hclstrtok_trans_keys[_mid]:
|
||||
_lower = _mid + 1
|
||||
default:
|
||||
_trans += int(_mid - int(_keys))
|
||||
goto _match
|
||||
}
|
||||
}
|
||||
_keys += _klen
|
||||
_trans += _klen
|
||||
}
|
||||
|
||||
_klen = int(_hclstrtok_range_lengths[cs])
|
||||
if _klen > 0 {
|
||||
_lower := int(_keys)
|
||||
var _mid int
|
||||
_upper := int(_keys + (_klen << 1) - 2)
|
||||
for {
|
||||
if _upper < _lower {
|
||||
break
|
||||
}
|
||||
|
||||
_mid = _lower + (((_upper - _lower) >> 1) & ^1)
|
||||
switch {
|
||||
case data[p] < _hclstrtok_trans_keys[_mid]:
|
||||
_upper = _mid - 2
|
||||
case data[p] > _hclstrtok_trans_keys[_mid+1]:
|
||||
_lower = _mid + 2
|
||||
default:
|
||||
_trans += int((_mid - int(_keys)) >> 1)
|
||||
goto _match
|
||||
}
|
||||
}
|
||||
_trans += _klen
|
||||
}
|
||||
|
||||
_match:
|
||||
_trans = int(_hclstrtok_indicies[_trans])
|
||||
cs = int(_hclstrtok_trans_targs[_trans])
|
||||
|
||||
if _hclstrtok_trans_actions[_trans] == 0 {
|
||||
goto _again
|
||||
}
|
||||
|
||||
_acts = int(_hclstrtok_trans_actions[_trans])
|
||||
_nacts = uint(_hclstrtok_actions[_acts])
|
||||
_acts++
|
||||
for ; _nacts > 0; _nacts-- {
|
||||
_acts++
|
||||
switch _hclstrtok_actions[_acts-1] {
|
||||
case 0:
|
||||
//line scan_string_lit.rl:40
|
||||
|
||||
// If te is behind p then we've skipped over some literal
|
||||
// characters which we must now return.
|
||||
if te < p {
|
||||
ret = append(ret, data[te:p])
|
||||
}
|
||||
ts = p
|
||||
|
||||
case 1:
|
||||
//line scan_string_lit.rl:48
|
||||
|
||||
te = p
|
||||
ret = append(ret, data[ts:te])
|
||||
|
||||
//line scan_string_lit.go:253
|
||||
}
|
||||
}
|
||||
|
||||
_again:
|
||||
if cs == 0 {
|
||||
goto _out
|
||||
}
|
||||
p++
|
||||
if p != pe {
|
||||
goto _resume
|
||||
}
|
||||
_test_eof:
|
||||
{
|
||||
}
|
||||
if p == eof {
|
||||
__acts := _hclstrtok_eof_actions[cs]
|
||||
__nacts := uint(_hclstrtok_actions[__acts])
|
||||
__acts++
|
||||
for ; __nacts > 0; __nacts-- {
|
||||
__acts++
|
||||
switch _hclstrtok_actions[__acts-1] {
|
||||
case 1:
|
||||
//line scan_string_lit.rl:48
|
||||
|
||||
te = p
|
||||
ret = append(ret, data[ts:te])
|
||||
|
||||
//line scan_string_lit.go:278
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_out:
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
//line scan_string_lit.rl:89
|
||||
|
||||
if te < p {
|
||||
// Collect any leftover literal characters at the end of the input
|
||||
ret = append(ret, data[te:p])
|
||||
}
|
||||
|
||||
// If we fall out here without being in a final state then we've
|
||||
// encountered something that the scanner can't match, which should
|
||||
// be impossible (the scanner matches all bytes _somehow_) but we'll
|
||||
// tolerate it and let the caller deal with it.
|
||||
if cs < hclstrtok_first_final {
|
||||
ret = append(ret, data[p:len(data)])
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
105
vendor/github.com/hashicorp/hcl/v2/hclsyntax/scan_string_lit.rl
generated
vendored
Normal file
105
vendor/github.com/hashicorp/hcl/v2/hclsyntax/scan_string_lit.rl
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
|
||||
package hclsyntax
|
||||
|
||||
// This file is generated from scan_string_lit.rl. DO NOT EDIT.
|
||||
%%{
|
||||
# (except you are actually in scan_string_lit.rl here, so edit away!)
|
||||
|
||||
machine hclstrtok;
|
||||
write data;
|
||||
}%%
|
||||
|
||||
func scanStringLit(data []byte, quoted bool) [][]byte {
|
||||
var ret [][]byte
|
||||
|
||||
%%{
|
||||
include UnicodeDerived "unicode_derived.rl";
|
||||
|
||||
UTF8Cont = 0x80 .. 0xBF;
|
||||
AnyUTF8 = (
|
||||
0x00..0x7F |
|
||||
0xC0..0xDF . UTF8Cont |
|
||||
0xE0..0xEF . UTF8Cont . UTF8Cont |
|
||||
0xF0..0xF7 . UTF8Cont . UTF8Cont . UTF8Cont
|
||||
);
|
||||
BadUTF8 = any - AnyUTF8;
|
||||
|
||||
Hex = ('0'..'9' | 'a'..'f' | 'A'..'F');
|
||||
|
||||
# Our goal with this patterns is to capture user intent as best as
|
||||
# possible, even if the input is invalid. The caller will then verify
|
||||
# whether each token is valid and generate suitable error messages
|
||||
# if not.
|
||||
UnicodeEscapeShort = "\\u" . Hex{0,4};
|
||||
UnicodeEscapeLong = "\\U" . Hex{0,8};
|
||||
UnicodeEscape = (UnicodeEscapeShort | UnicodeEscapeLong);
|
||||
SimpleEscape = "\\" . (AnyUTF8 - ('U'|'u'))?;
|
||||
TemplateEscape = ("$" . ("$" . ("{"?))?) | ("%" . ("%" . ("{"?))?);
|
||||
Newline = ("\r\n" | "\r" | "\n");
|
||||
|
||||
action Begin {
|
||||
// If te is behind p then we've skipped over some literal
|
||||
// characters which we must now return.
|
||||
if te < p {
|
||||
ret = append(ret, data[te:p])
|
||||
}
|
||||
ts = p;
|
||||
}
|
||||
action End {
|
||||
te = p;
|
||||
ret = append(ret, data[ts:te]);
|
||||
}
|
||||
|
||||
QuotedToken = (UnicodeEscape | SimpleEscape | TemplateEscape | Newline) >Begin %End;
|
||||
UnquotedToken = (TemplateEscape | Newline) >Begin %End;
|
||||
QuotedLiteral = (any - ("\\" | "$" | "%" | "\r" | "\n"));
|
||||
UnquotedLiteral = (any - ("$" | "%" | "\r" | "\n"));
|
||||
|
||||
quoted := (QuotedToken | QuotedLiteral)**;
|
||||
unquoted := (UnquotedToken | UnquotedLiteral)**;
|
||||
|
||||
}%%
|
||||
|
||||
// Ragel state
|
||||
p := 0 // "Pointer" into data
|
||||
pe := len(data) // End-of-data "pointer"
|
||||
ts := 0
|
||||
te := 0
|
||||
eof := pe
|
||||
|
||||
var cs int // current state
|
||||
switch {
|
||||
case quoted:
|
||||
cs = hclstrtok_en_quoted
|
||||
default:
|
||||
cs = hclstrtok_en_unquoted
|
||||
}
|
||||
|
||||
// Make Go compiler happy
|
||||
_ = ts
|
||||
_ = eof
|
||||
|
||||
/*token := func () {
|
||||
ret = append(ret, data[ts:te])
|
||||
}*/
|
||||
|
||||
%%{
|
||||
write init nocs;
|
||||
write exec;
|
||||
}%%
|
||||
|
||||
if te < p {
|
||||
// Collect any leftover literal characters at the end of the input
|
||||
ret = append(ret, data[te:p])
|
||||
}
|
||||
|
||||
// If we fall out here without being in a final state then we've
|
||||
// encountered something that the scanner can't match, which should
|
||||
// be impossible (the scanner matches all bytes _somehow_) but we'll
|
||||
// tolerate it and let the caller deal with it.
|
||||
if cs < hclstrtok_first_final {
|
||||
ret = append(ret, data[p:len(data)])
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
5265
vendor/github.com/hashicorp/hcl/v2/hclsyntax/scan_tokens.go
generated
vendored
Normal file
5265
vendor/github.com/hashicorp/hcl/v2/hclsyntax/scan_tokens.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
395
vendor/github.com/hashicorp/hcl/v2/hclsyntax/scan_tokens.rl
generated
vendored
Normal file
395
vendor/github.com/hashicorp/hcl/v2/hclsyntax/scan_tokens.rl
generated
vendored
Normal file
@ -0,0 +1,395 @@
|
||||
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// This file is generated from scan_tokens.rl. DO NOT EDIT.
|
||||
%%{
|
||||
# (except when you are actually in scan_tokens.rl here, so edit away!)
|
||||
|
||||
machine hcltok;
|
||||
write data;
|
||||
}%%
|
||||
|
||||
func scanTokens(data []byte, filename string, start hcl.Pos, mode scanMode) []Token {
|
||||
stripData := stripUTF8BOM(data)
|
||||
start.Byte += len(data) - len(stripData)
|
||||
data = stripData
|
||||
|
||||
f := &tokenAccum{
|
||||
Filename: filename,
|
||||
Bytes: data,
|
||||
Pos: start,
|
||||
StartByte: start.Byte,
|
||||
}
|
||||
|
||||
%%{
|
||||
include UnicodeDerived "unicode_derived.rl";
|
||||
|
||||
UTF8Cont = 0x80 .. 0xBF;
|
||||
AnyUTF8 = (
|
||||
0x00..0x7F |
|
||||
0xC0..0xDF . UTF8Cont |
|
||||
0xE0..0xEF . UTF8Cont . UTF8Cont |
|
||||
0xF0..0xF7 . UTF8Cont . UTF8Cont . UTF8Cont
|
||||
);
|
||||
BrokenUTF8 = any - AnyUTF8;
|
||||
|
||||
NumberLitContinue = (digit|'.'|('e'|'E') ('+'|'-')? digit);
|
||||
NumberLit = digit ("" | (NumberLitContinue - '.') | (NumberLitContinue* (NumberLitContinue - '.')));
|
||||
Ident = (ID_Start | '_') (ID_Continue | '-')*;
|
||||
|
||||
# Symbols that just represent themselves are handled as a single rule.
|
||||
SelfToken = "[" | "]" | "(" | ")" | "." | "," | "*" | "/" | "%" | "+" | "-" | "=" | "<" | ">" | "!" | "?" | ":" | "\n" | "&" | "|" | "~" | "^" | ";" | "`" | "'";
|
||||
|
||||
EqualOp = "==";
|
||||
NotEqual = "!=";
|
||||
GreaterThanEqual = ">=";
|
||||
LessThanEqual = "<=";
|
||||
LogicalAnd = "&&";
|
||||
LogicalOr = "||";
|
||||
|
||||
Ellipsis = "...";
|
||||
FatArrow = "=>";
|
||||
|
||||
Newline = '\r' ? '\n';
|
||||
EndOfLine = Newline;
|
||||
|
||||
BeginStringTmpl = '"';
|
||||
BeginHeredocTmpl = '<<' ('-')? Ident Newline;
|
||||
|
||||
Comment = (
|
||||
# The :>> operator in these is a "finish-guarded concatenation",
|
||||
# which terminates the sequence on its left when it completes
|
||||
# the sequence on its right.
|
||||
# In the single-line comment cases this is allowing us to make
|
||||
# the trailing EndOfLine optional while still having the overall
|
||||
# pattern terminate. In the multi-line case it ensures that
|
||||
# the first comment in the file ends at the first */, rather than
|
||||
# gobbling up all of the "any*" until the _final_ */ in the file.
|
||||
("#" (any - EndOfLine)* :>> EndOfLine?) |
|
||||
("//" (any - EndOfLine)* :>> EndOfLine?) |
|
||||
("/*" any* :>> "*/")
|
||||
);
|
||||
|
||||
# Note: hclwrite assumes that only ASCII spaces appear between tokens,
|
||||
# and uses this assumption to recreate the spaces between tokens by
|
||||
# looking at byte offset differences. This means it will produce
|
||||
# incorrect results in the presence of tabs, but that's acceptable
|
||||
# because the canonical style (which hclwrite itself can impose
|
||||
# automatically is to never use tabs).
|
||||
Spaces = (' ' | 0x09)+;
|
||||
|
||||
action beginStringTemplate {
|
||||
token(TokenOQuote);
|
||||
fcall stringTemplate;
|
||||
}
|
||||
|
||||
action endStringTemplate {
|
||||
token(TokenCQuote);
|
||||
fret;
|
||||
}
|
||||
|
||||
action beginHeredocTemplate {
|
||||
token(TokenOHeredoc);
|
||||
// the token is currently the whole heredoc introducer, like
|
||||
// <<EOT or <<-EOT, followed by a newline. We want to extract
|
||||
// just the "EOT" portion that we'll use as the closing marker.
|
||||
|
||||
marker := data[ts+2:te-1]
|
||||
if marker[0] == '-' {
|
||||
marker = marker[1:]
|
||||
}
|
||||
if marker[len(marker)-1] == '\r' {
|
||||
marker = marker[:len(marker)-1]
|
||||
}
|
||||
|
||||
heredocs = append(heredocs, heredocInProgress{
|
||||
Marker: marker,
|
||||
StartOfLine: true,
|
||||
})
|
||||
|
||||
fcall heredocTemplate;
|
||||
}
|
||||
|
||||
action heredocLiteralEOL {
|
||||
// This action is called specificially when a heredoc literal
|
||||
// ends with a newline character.
|
||||
|
||||
// This might actually be our end marker.
|
||||
topdoc := &heredocs[len(heredocs)-1]
|
||||
if topdoc.StartOfLine {
|
||||
maybeMarker := bytes.TrimSpace(data[ts:te])
|
||||
if bytes.Equal(maybeMarker, topdoc.Marker) {
|
||||
// We actually emit two tokens here: the end-of-heredoc
|
||||
// marker first, and then separately the newline that
|
||||
// follows it. This then avoids issues with the closing
|
||||
// marker consuming a newline that would normally be used
|
||||
// to mark the end of an attribute definition.
|
||||
// We might have either a \n sequence or an \r\n sequence
|
||||
// here, so we must handle both.
|
||||
nls := te-1
|
||||
nle := te
|
||||
te--
|
||||
if data[te-1] == '\r' {
|
||||
// back up one more byte
|
||||
nls--
|
||||
te--
|
||||
}
|
||||
token(TokenCHeredoc);
|
||||
ts = nls
|
||||
te = nle
|
||||
token(TokenNewline);
|
||||
heredocs = heredocs[:len(heredocs)-1]
|
||||
fret;
|
||||
}
|
||||
}
|
||||
|
||||
topdoc.StartOfLine = true;
|
||||
token(TokenStringLit);
|
||||
}
|
||||
|
||||
action heredocLiteralMidline {
|
||||
// This action is called when a heredoc literal _doesn't_ end
|
||||
// with a newline character, e.g. because we're about to enter
|
||||
// an interpolation sequence.
|
||||
heredocs[len(heredocs)-1].StartOfLine = false;
|
||||
token(TokenStringLit);
|
||||
}
|
||||
|
||||
action bareTemplateLiteral {
|
||||
token(TokenStringLit);
|
||||
}
|
||||
|
||||
action beginTemplateInterp {
|
||||
token(TokenTemplateInterp);
|
||||
braces++;
|
||||
retBraces = append(retBraces, braces);
|
||||
if len(heredocs) > 0 {
|
||||
heredocs[len(heredocs)-1].StartOfLine = false;
|
||||
}
|
||||
fcall main;
|
||||
}
|
||||
|
||||
action beginTemplateControl {
|
||||
token(TokenTemplateControl);
|
||||
braces++;
|
||||
retBraces = append(retBraces, braces);
|
||||
if len(heredocs) > 0 {
|
||||
heredocs[len(heredocs)-1].StartOfLine = false;
|
||||
}
|
||||
fcall main;
|
||||
}
|
||||
|
||||
action openBrace {
|
||||
token(TokenOBrace);
|
||||
braces++;
|
||||
}
|
||||
|
||||
action closeBrace {
|
||||
if len(retBraces) > 0 && retBraces[len(retBraces)-1] == braces {
|
||||
token(TokenTemplateSeqEnd);
|
||||
braces--;
|
||||
retBraces = retBraces[0:len(retBraces)-1]
|
||||
fret;
|
||||
} else {
|
||||
token(TokenCBrace);
|
||||
braces--;
|
||||
}
|
||||
}
|
||||
|
||||
action closeTemplateSeqEatWhitespace {
|
||||
// Only consume from the retBraces stack and return if we are at
|
||||
// a suitable brace nesting level, otherwise things will get
|
||||
// confused. (Not entering this branch indicates a syntax error,
|
||||
// which we will catch in the parser.)
|
||||
if len(retBraces) > 0 && retBraces[len(retBraces)-1] == braces {
|
||||
token(TokenTemplateSeqEnd);
|
||||
braces--;
|
||||
retBraces = retBraces[0:len(retBraces)-1]
|
||||
fret;
|
||||
} else {
|
||||
// We intentionally generate a TokenTemplateSeqEnd here,
|
||||
// even though the user apparently wanted a brace, because
|
||||
// we want to allow the parser to catch the incorrect use
|
||||
// of a ~} to balance a generic opening brace, rather than
|
||||
// a template sequence.
|
||||
token(TokenTemplateSeqEnd);
|
||||
braces--;
|
||||
}
|
||||
}
|
||||
|
||||
TemplateInterp = "${" ("~")?;
|
||||
TemplateControl = "%{" ("~")?;
|
||||
EndStringTmpl = '"';
|
||||
NewlineChars = ("\r"|"\n");
|
||||
NewlineCharsSeq = NewlineChars+;
|
||||
StringLiteralChars = (AnyUTF8 - NewlineChars);
|
||||
TemplateIgnoredNonBrace = (^'{' %{ fhold; });
|
||||
TemplateNotInterp = '$' (TemplateIgnoredNonBrace | TemplateInterp);
|
||||
TemplateNotControl = '%' (TemplateIgnoredNonBrace | TemplateControl);
|
||||
QuotedStringLiteralWithEsc = ('\\' StringLiteralChars) | (StringLiteralChars - ("$" | '%' | '"' | "\\"));
|
||||
TemplateStringLiteral = (
|
||||
(TemplateNotInterp) |
|
||||
(TemplateNotControl) |
|
||||
(QuotedStringLiteralWithEsc)+
|
||||
);
|
||||
HeredocStringLiteral = (
|
||||
(TemplateNotInterp) |
|
||||
(TemplateNotControl) |
|
||||
(StringLiteralChars - ("$" | '%'))*
|
||||
);
|
||||
BareStringLiteral = (
|
||||
(TemplateNotInterp) |
|
||||
(TemplateNotControl) |
|
||||
(StringLiteralChars - ("$" | '%'))*
|
||||
) Newline?;
|
||||
|
||||
stringTemplate := |*
|
||||
TemplateInterp => beginTemplateInterp;
|
||||
TemplateControl => beginTemplateControl;
|
||||
EndStringTmpl => endStringTemplate;
|
||||
TemplateStringLiteral => { token(TokenQuotedLit); };
|
||||
NewlineCharsSeq => { token(TokenQuotedNewline); };
|
||||
AnyUTF8 => { token(TokenInvalid); };
|
||||
BrokenUTF8 => { token(TokenBadUTF8); };
|
||||
*|;
|
||||
|
||||
heredocTemplate := |*
|
||||
TemplateInterp => beginTemplateInterp;
|
||||
TemplateControl => beginTemplateControl;
|
||||
HeredocStringLiteral EndOfLine => heredocLiteralEOL;
|
||||
HeredocStringLiteral => heredocLiteralMidline;
|
||||
BrokenUTF8 => { token(TokenBadUTF8); };
|
||||
*|;
|
||||
|
||||
bareTemplate := |*
|
||||
TemplateInterp => beginTemplateInterp;
|
||||
TemplateControl => beginTemplateControl;
|
||||
BareStringLiteral => bareTemplateLiteral;
|
||||
BrokenUTF8 => { token(TokenBadUTF8); };
|
||||
*|;
|
||||
|
||||
identOnly := |*
|
||||
Ident => { token(TokenIdent) };
|
||||
BrokenUTF8 => { token(TokenBadUTF8) };
|
||||
AnyUTF8 => { token(TokenInvalid) };
|
||||
*|;
|
||||
|
||||
main := |*
|
||||
Spaces => {};
|
||||
NumberLit => { token(TokenNumberLit) };
|
||||
Ident => { token(TokenIdent) };
|
||||
|
||||
Comment => { token(TokenComment) };
|
||||
Newline => { token(TokenNewline) };
|
||||
|
||||
EqualOp => { token(TokenEqualOp); };
|
||||
NotEqual => { token(TokenNotEqual); };
|
||||
GreaterThanEqual => { token(TokenGreaterThanEq); };
|
||||
LessThanEqual => { token(TokenLessThanEq); };
|
||||
LogicalAnd => { token(TokenAnd); };
|
||||
LogicalOr => { token(TokenOr); };
|
||||
Ellipsis => { token(TokenEllipsis); };
|
||||
FatArrow => { token(TokenFatArrow); };
|
||||
SelfToken => { selfToken() };
|
||||
|
||||
"{" => openBrace;
|
||||
"}" => closeBrace;
|
||||
|
||||
"~}" => closeTemplateSeqEatWhitespace;
|
||||
|
||||
BeginStringTmpl => beginStringTemplate;
|
||||
BeginHeredocTmpl => beginHeredocTemplate;
|
||||
|
||||
BrokenUTF8 => { token(TokenBadUTF8) };
|
||||
AnyUTF8 => { token(TokenInvalid) };
|
||||
*|;
|
||||
|
||||
}%%
|
||||
|
||||
// Ragel state
|
||||
p := 0 // "Pointer" into data
|
||||
pe := len(data) // End-of-data "pointer"
|
||||
ts := 0
|
||||
te := 0
|
||||
act := 0
|
||||
eof := pe
|
||||
var stack []int
|
||||
var top int
|
||||
|
||||
var cs int // current state
|
||||
switch mode {
|
||||
case scanNormal:
|
||||
cs = hcltok_en_main
|
||||
case scanTemplate:
|
||||
cs = hcltok_en_bareTemplate
|
||||
case scanIdentOnly:
|
||||
cs = hcltok_en_identOnly
|
||||
default:
|
||||
panic("invalid scanMode")
|
||||
}
|
||||
|
||||
braces := 0
|
||||
var retBraces []int // stack of brace levels that cause us to use fret
|
||||
var heredocs []heredocInProgress // stack of heredocs we're currently processing
|
||||
|
||||
%%{
|
||||
prepush {
|
||||
stack = append(stack, 0);
|
||||
}
|
||||
postpop {
|
||||
stack = stack[:len(stack)-1];
|
||||
}
|
||||
}%%
|
||||
|
||||
// Make Go compiler happy
|
||||
_ = ts
|
||||
_ = te
|
||||
_ = act
|
||||
_ = eof
|
||||
|
||||
token := func (ty TokenType) {
|
||||
f.emitToken(ty, ts, te)
|
||||
}
|
||||
selfToken := func () {
|
||||
b := data[ts:te]
|
||||
if len(b) != 1 {
|
||||
// should never happen
|
||||
panic("selfToken only works for single-character tokens")
|
||||
}
|
||||
f.emitToken(TokenType(b[0]), ts, te)
|
||||
}
|
||||
|
||||
%%{
|
||||
write init nocs;
|
||||
write exec;
|
||||
}%%
|
||||
|
||||
// If we fall out here without being in a final state then we've
|
||||
// encountered something that the scanner can't match, which we'll
|
||||
// deal with as an invalid.
|
||||
if cs < hcltok_first_final {
|
||||
if mode == scanTemplate && len(stack) == 0 {
|
||||
// If we're scanning a bare template then any straggling
|
||||
// top-level stuff is actually literal string, rather than
|
||||
// invalid. This handles the case where the template ends
|
||||
// with a single "$" or "%", which trips us up because we
|
||||
// want to see another character to decide if it's a sequence
|
||||
// or an escape.
|
||||
f.emitToken(TokenStringLit, ts, len(data))
|
||||
} else {
|
||||
f.emitToken(TokenInvalid, ts, len(data))
|
||||
}
|
||||
}
|
||||
|
||||
// We always emit a synthetic EOF token at the end, since it gives the
|
||||
// parser position information for an "unexpected EOF" diagnostic.
|
||||
f.emitToken(TokenEOF, len(data), len(data))
|
||||
|
||||
return f.Tokens
|
||||
}
|
941
vendor/github.com/hashicorp/hcl/v2/hclsyntax/spec.md
generated
vendored
Normal file
941
vendor/github.com/hashicorp/hcl/v2/hclsyntax/spec.md
generated
vendored
Normal file
@ -0,0 +1,941 @@
|
||||
# HCL Native Syntax Specification
|
||||
|
||||
This is the specification of the syntax and semantics of the native syntax
|
||||
for HCL. HCL is a system for defining configuration languages for applications.
|
||||
The HCL information model is designed to support multiple concrete syntaxes
|
||||
for configuration, but this native syntax is considered the primary format
|
||||
and is optimized for human authoring and maintenance, as opposed to machine
|
||||
generation of configuration.
|
||||
|
||||
The language consists of three integrated sub-languages:
|
||||
|
||||
- The _structural_ language defines the overall hierarchical configuration
|
||||
structure, and is a serialization of HCL bodies, blocks and attributes.
|
||||
|
||||
- The _expression_ language is used to express attribute values, either as
|
||||
literals or as derivations of other values.
|
||||
|
||||
- The _template_ language is used to compose values together into strings,
|
||||
as one of several types of expression in the expression language.
|
||||
|
||||
In normal use these three sub-languages are used together within configuration
|
||||
files to describe an overall configuration, with the structural language
|
||||
being used at the top level. The expression and template languages can also
|
||||
be used in isolation, to implement features such as REPLs, debuggers, and
|
||||
integration into more limited HCL syntaxes such as the JSON profile.
|
||||
|
||||
## Syntax Notation
|
||||
|
||||
Within this specification a semi-formal notation is used to illustrate the
|
||||
details of syntax. This notation is intended for human consumption rather
|
||||
than machine consumption, with the following conventions:
|
||||
|
||||
- A naked name starting with an uppercase letter is a global production,
|
||||
common to all of the syntax specifications in this document.
|
||||
- A naked name starting with a lowercase letter is a local production,
|
||||
meaningful only within the specification where it is defined.
|
||||
- Double and single quotes (`"` and `'`) are used to mark literal character
|
||||
sequences, which may be either punctuation markers or keywords.
|
||||
- The default operator for combining items, which has no punctuation,
|
||||
is concatenation.
|
||||
- The symbol `|` indicates that any one of its left and right operands may
|
||||
be present.
|
||||
- The `*` symbol indicates zero or more repetitions of the item to its left.
|
||||
- The `?` symbol indicates zero or one of the item to its left.
|
||||
- Parentheses (`(` and `)`) are used to group items together to apply
|
||||
the `|`, `*` and `?` operators to them collectively.
|
||||
|
||||
The grammar notation does not fully describe the language. The prose may
|
||||
augment or conflict with the illustrated grammar. In case of conflict, prose
|
||||
has priority.
|
||||
|
||||
## Source Code Representation
|
||||
|
||||
Source code is unicode text expressed in the UTF-8 encoding. The language
|
||||
itself does not perform unicode normalization, so syntax features such as
|
||||
identifiers are sequences of unicode code points and so e.g. a precombined
|
||||
accented character is distinct from a letter associated with a combining
|
||||
accent. (String literals have some special handling with regard to Unicode
|
||||
normalization which will be covered later in the relevant section.)
|
||||
|
||||
UTF-8 encoded Unicode byte order marks are not permitted. Invalid or
|
||||
non-normalized UTF-8 encoding is always a parse error.
|
||||
|
||||
## Lexical Elements
|
||||
|
||||
### Comments and Whitespace
|
||||
|
||||
Comments and Whitespace are recognized as lexical elements but are ignored
|
||||
except as described below.
|
||||
|
||||
Whitespace is defined as a sequence of zero or more space characters
|
||||
(U+0020). Newline sequences (either U+000A or U+000D followed by U+000A)
|
||||
are _not_ considered whitespace but are ignored as such in certain contexts.
|
||||
Horizontal tab characters (U+0009) are also treated as whitespace, but are
|
||||
counted only as one "column" for the purpose of reporting source positions.
|
||||
|
||||
Comments serve as program documentation and come in two forms:
|
||||
|
||||
- _Line comments_ start with either the `//` or `#` sequences and end with
|
||||
the next newline sequence. A line comment is considered equivalent to a
|
||||
newline sequence.
|
||||
|
||||
- _Inline comments_ start with the `/*` sequence and end with the `*/`
|
||||
sequence, and may have any characters within except the ending sequence.
|
||||
An inline comments is considered equivalent to a whitespace sequence.
|
||||
|
||||
Comments and whitespace cannot begin within within other comments, or within
|
||||
template literals except inside an interpolation sequence or template directive.
|
||||
|
||||
### Identifiers
|
||||
|
||||
Identifiers name entities such as blocks, attributes and expression variables.
|
||||
Identifiers are interpreted as per [UAX #31][uax31] Section 2. Specifically,
|
||||
their syntax is defined in terms of the `ID_Start` and `ID_Continue`
|
||||
character properties as follows:
|
||||
|
||||
```ebnf
|
||||
Identifier = ID_Start (ID_Continue | '-')*;
|
||||
```
|
||||
|
||||
The Unicode specification provides the normative requirements for identifier
|
||||
parsing. Non-normatively, the spirit of this specification is that `ID_Start`
|
||||
consists of Unicode letter and certain unambiguous punctuation tokens, while
|
||||
`ID_Continue` augments that set with Unicode digits, combining marks, etc.
|
||||
|
||||
The dash character `-` is additionally allowed in identifiers, even though
|
||||
that is not part of the unicode `ID_Continue` definition. This is to allow
|
||||
attribute names and block type names to contain dashes, although underscores
|
||||
as word separators are considered the idiomatic usage.
|
||||
|
||||
[uax31]: http://unicode.org/reports/tr31/ "Unicode Identifier and Pattern Syntax"
|
||||
|
||||
### Keywords
|
||||
|
||||
There are no globally-reserved words, but in some contexts certain identifiers
|
||||
are reserved to function as keywords. These are discussed further in the
|
||||
relevant documentation sections that follow. In such situations, the
|
||||
identifier's role as a keyword supersedes any other valid interpretation that
|
||||
may be possible. Outside of these specific situations, the keywords have no
|
||||
special meaning and are interpreted as regular identifiers.
|
||||
|
||||
### Operators and Delimiters
|
||||
|
||||
The following character sequences represent operators, delimiters, and other
|
||||
special tokens:
|
||||
|
||||
```
|
||||
+ && == < : { [ ( ${
|
||||
- || != > ? } ] ) %{
|
||||
* ! <= = .
|
||||
/ >= => ,
|
||||
% ...
|
||||
```
|
||||
|
||||
### Numeric Literals
|
||||
|
||||
A numeric literal is a decimal representation of a
|
||||
real number. It has an integer part, a fractional part,
|
||||
and an exponent part.
|
||||
|
||||
```ebnf
|
||||
NumericLit = decimal+ ("." decimal+)? (expmark decimal+)?;
|
||||
decimal = '0' .. '9';
|
||||
expmark = ('e' | 'E') ("+" | "-")?;
|
||||
```
|
||||
|
||||
## Structural Elements
|
||||
|
||||
The structural language consists of syntax representing the following
|
||||
constructs:
|
||||
|
||||
- _Attributes_, which assign a value to a specified name.
|
||||
- _Blocks_, which create a child body annotated by a type and optional labels.
|
||||
- _Body Content_, which consists of a collection of attributes and blocks.
|
||||
|
||||
These constructs correspond to the similarly-named concepts in the
|
||||
language-agnostic HCL information model.
|
||||
|
||||
```ebnf
|
||||
ConfigFile = Body;
|
||||
Body = (Attribute | Block | OneLineBlock)*;
|
||||
Attribute = Identifier "=" Expression Newline;
|
||||
Block = Identifier (StringLit|Identifier)* "{" Newline Body "}" Newline;
|
||||
OneLineBlock = Identifier (StringLit|Identifier)* "{" (Identifier "=" Expression)? "}" Newline;
|
||||
```
|
||||
|
||||
### Configuration Files
|
||||
|
||||
A _configuration file_ is a sequence of characters whose top-level is
|
||||
interpreted as a Body.
|
||||
|
||||
### Bodies
|
||||
|
||||
A _body_ is a collection of associated attributes and blocks. The meaning of
|
||||
this association is defined by the calling application.
|
||||
|
||||
### Attribute Definitions
|
||||
|
||||
An _attribute definition_ assigns a value to a particular attribute name within
|
||||
a body. Each distinct attribute name may be defined no more than once within a
|
||||
single body.
|
||||
|
||||
The attribute value is given as an expression, which is retained literally
|
||||
for later evaluation by the calling application.
|
||||
|
||||
### Blocks
|
||||
|
||||
A _block_ creates a child body that is annotated with a block _type_ and
|
||||
zero or more block _labels_. Blocks create a structural hierarchy which can be
|
||||
interpreted by the calling application.
|
||||
|
||||
Block labels can either be quoted literal strings or naked identifiers.
|
||||
|
||||
## Expressions
|
||||
|
||||
The expression sub-language is used within attribute definitions to specify
|
||||
values.
|
||||
|
||||
```ebnf
|
||||
Expression = (
|
||||
ExprTerm |
|
||||
Operation |
|
||||
Conditional
|
||||
);
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
The value types used within the expression language are those defined by the
|
||||
syntax-agnostic HCL information model. An expression may return any valid
|
||||
type, but only a subset of the available types have first-class syntax.
|
||||
A calling application may make other types available via _variables_ and
|
||||
_functions_.
|
||||
|
||||
### Expression Terms
|
||||
|
||||
Expression _terms_ are the operands for unary and binary expressions, as well
|
||||
as acting as expressions in their own right.
|
||||
|
||||
```ebnf
|
||||
ExprTerm = (
|
||||
LiteralValue |
|
||||
CollectionValue |
|
||||
TemplateExpr |
|
||||
VariableExpr |
|
||||
FunctionCall |
|
||||
ForExpr |
|
||||
ExprTerm Index |
|
||||
ExprTerm GetAttr |
|
||||
ExprTerm Splat |
|
||||
"(" Expression ")"
|
||||
);
|
||||
```
|
||||
|
||||
The productions for these different term types are given in their corresponding
|
||||
sections.
|
||||
|
||||
Between the `(` and `)` characters denoting a sub-expression, newline
|
||||
characters are ignored as whitespace.
|
||||
|
||||
### Literal Values
|
||||
|
||||
A _literal value_ immediately represents a particular value of a primitive
|
||||
type.
|
||||
|
||||
```ebnf
|
||||
LiteralValue = (
|
||||
NumericLit |
|
||||
"true" |
|
||||
"false" |
|
||||
"null"
|
||||
);
|
||||
```
|
||||
|
||||
- Numeric literals represent values of type _number_.
|
||||
- The `true` and `false` keywords represent values of type _bool_.
|
||||
- The `null` keyword represents a null value of the dynamic pseudo-type.
|
||||
|
||||
String literals are not directly available in the expression sub-language, but
|
||||
are available via the template sub-language, which can in turn be incorporated
|
||||
via _template expressions_.
|
||||
|
||||
### Collection Values
|
||||
|
||||
A _collection value_ combines zero or more other expressions to produce a
|
||||
collection value.
|
||||
|
||||
```ebnf
|
||||
CollectionValue = tuple | object;
|
||||
tuple = "[" (
|
||||
(Expression ("," Expression)* ","?)?
|
||||
) "]";
|
||||
object = "{" (
|
||||
(objectelem ("," objectelem)* ","?)?
|
||||
) "}";
|
||||
objectelem = (Identifier | Expression) ("=" | ":") Expression;
|
||||
```
|
||||
|
||||
Only tuple and object values can be directly constructed via native syntax.
|
||||
Tuple and object values can in turn be converted to list, set and map values
|
||||
with other operations, which behaves as defined by the syntax-agnostic HCL
|
||||
information model.
|
||||
|
||||
When specifying an object element, an identifier is interpreted as a literal
|
||||
attribute name as opposed to a variable reference. To populate an item key
|
||||
from a variable, use parentheses to disambiguate:
|
||||
|
||||
- `{foo = "baz"}` is interpreted as an attribute literally named `foo`.
|
||||
- `{(foo) = "baz"}` is interpreted as an attribute whose name is taken
|
||||
from the variable named `foo`.
|
||||
|
||||
Between the open and closing delimiters of these sequences, newline sequences
|
||||
are ignored as whitespace.
|
||||
|
||||
There is a syntax ambiguity between _for expressions_ and collection values
|
||||
whose first element is a reference to a variable named `for`. The
|
||||
_for expression_ interpretation has priority, so to produce a tuple whose
|
||||
first element is the value of a variable named `for`, or an object with a
|
||||
key named `for`, use parentheses to disambiguate:
|
||||
|
||||
- `[for, foo, baz]` is a syntax error.
|
||||
- `[(for), foo, baz]` is a tuple whose first element is the value of variable
|
||||
`for`.
|
||||
- `{for: 1, baz: 2}` is a syntax error.
|
||||
- `{(for): 1, baz: 2}` is an object with an attribute literally named `for`.
|
||||
- `{baz: 2, for: 1}` is equivalent to the previous example, and resolves the
|
||||
ambiguity by reordering.
|
||||
|
||||
### Template Expressions
|
||||
|
||||
A _template expression_ embeds a program written in the template sub-language
|
||||
as an expression. Template expressions come in two forms:
|
||||
|
||||
- A _quoted_ template expression is delimited by quote characters (`"`) and
|
||||
defines a template as a single-line expression with escape characters.
|
||||
- A _heredoc_ template expression is introduced by a `<<` sequence and
|
||||
defines a template via a multi-line sequence terminated by a user-chosen
|
||||
delimiter.
|
||||
|
||||
In both cases the template interpolation and directive syntax is available for
|
||||
use within the delimiters, and any text outside of these special sequences is
|
||||
interpreted as a literal string.
|
||||
|
||||
In _quoted_ template expressions any literal string sequences within the
|
||||
template behave in a special way: literal newline sequences are not permitted
|
||||
and instead _escape sequences_ can be included, starting with the
|
||||
backslash `\`:
|
||||
|
||||
```
|
||||
\n Unicode newline control character
|
||||
\r Unicode carriage return control character
|
||||
\t Unicode tab control character
|
||||
\" Literal quote mark, used to prevent interpretation as end of string
|
||||
\\ Literal backslash, used to prevent interpretation as escape sequence
|
||||
\uNNNN Unicode character from Basic Multilingual Plane (NNNN is four hexadecimal digits)
|
||||
\UNNNNNNNN Unicode character from supplementary planes (NNNNNNNN is eight hexadecimal digits)
|
||||
```
|
||||
|
||||
The _heredoc_ template expression type is introduced by either `<<` or `<<-`,
|
||||
followed by an identifier. The template expression ends when the given
|
||||
identifier subsequently appears again on a line of its own.
|
||||
|
||||
If a heredoc template is introduced with the `<<-` symbol, any literal string
|
||||
at the start of each line is analyzed to find the minimum number of leading
|
||||
spaces, and then that number of prefix spaces is removed from all line-leading
|
||||
literal strings. The final closing marker may also have an arbitrary number
|
||||
of spaces preceding it on its line.
|
||||
|
||||
```ebnf
|
||||
TemplateExpr = quotedTemplate | heredocTemplate;
|
||||
quotedTemplate = (as defined in prose above);
|
||||
heredocTemplate = (
|
||||
("<<" | "<<-") Identifier Newline
|
||||
(content as defined in prose above)
|
||||
Identifier Newline
|
||||
);
|
||||
```
|
||||
|
||||
A quoted template expression containing only a single literal string serves
|
||||
as a syntax for defining literal string _expressions_. In certain contexts
|
||||
the template syntax is restricted in this manner:
|
||||
|
||||
```ebnf
|
||||
StringLit = '"' (quoted literals as defined in prose above) '"';
|
||||
```
|
||||
|
||||
The `StringLit` production permits the escape sequences discussed for quoted
|
||||
template expressions as above, but does _not_ permit template interpolation
|
||||
or directive sequences.
|
||||
|
||||
### Variables and Variable Expressions
|
||||
|
||||
A _variable_ is a value that has been assigned a symbolic name. Variables are
|
||||
made available for use in expressions by the calling application, by populating
|
||||
the _global scope_ used for expression evaluation.
|
||||
|
||||
Variables can also be created by expressions themselves, which always creates
|
||||
a _child scope_ that incorporates the variables from its parent scope but
|
||||
(re-)defines zero or more names with new values.
|
||||
|
||||
The value of a variable is accessed using a _variable expression_, which is
|
||||
a standalone `Identifier` whose name corresponds to a defined variable:
|
||||
|
||||
```ebnf
|
||||
VariableExpr = Identifier;
|
||||
```
|
||||
|
||||
Variables in a particular scope are immutable, but child scopes may _hide_
|
||||
a variable from an ancestor scope by defining a new variable of the same name.
|
||||
When looking up variables, the most locally-defined variable of the given name
|
||||
is used, and ancestor-scoped variables of the same name cannot be accessed.
|
||||
|
||||
No direct syntax is provided for declaring or assigning variables, but other
|
||||
expression constructs implicitly create child scopes and define variables as
|
||||
part of their evaluation.
|
||||
|
||||
### Functions and Function Calls
|
||||
|
||||
A _function_ is an operation that has been assigned a symbolic name. Functions
|
||||
are made available for use in expressions by the calling application, by
|
||||
populating the _function table_ used for expression evaluation.
|
||||
|
||||
The namespace of functions is distinct from the namespace of variables. A
|
||||
function and a variable may share the same name with no implication that they
|
||||
are in any way related.
|
||||
|
||||
A function can be executed via a _function call_ expression:
|
||||
|
||||
```ebnf
|
||||
FunctionCall = Identifier "(" arguments ")";
|
||||
Arguments = (
|
||||
() ||
|
||||
(Expression ("," Expression)* ("," | "...")?)
|
||||
);
|
||||
```
|
||||
|
||||
The definition of functions and the semantics of calling them are defined by
|
||||
the language-agnostic HCL information model. The given arguments are mapped
|
||||
onto the function's _parameters_ and the result of a function call expression
|
||||
is the return value of the named function when given those arguments.
|
||||
|
||||
If the final argument expression is followed by the ellipsis symbol (`...`),
|
||||
the final argument expression must evaluate to either a list or tuple value.
|
||||
The elements of the value are each mapped to a single parameter of the
|
||||
named function, beginning at the first parameter remaining after all other
|
||||
argument expressions have been mapped.
|
||||
|
||||
Within the parentheses that delimit the function arguments, newline sequences
|
||||
are ignored as whitespace.
|
||||
|
||||
### For Expressions
|
||||
|
||||
A _for expression_ is a construct for constructing a collection by projecting
|
||||
the items from another collection.
|
||||
|
||||
```ebnf
|
||||
ForExpr = forTupleExpr | forObjectExpr;
|
||||
forTupleExpr = "[" forIntro Expression forCond? "]";
|
||||
forObjectExpr = "{" forIntro Expression "=>" Expression "..."? forCond? "}";
|
||||
forIntro = "for" Identifier ("," Identifier)? "in" Expression ":";
|
||||
forCond = "if" Expression;
|
||||
```
|
||||
|
||||
The punctuation used to delimit a for expression decide whether it will produce
|
||||
a tuple value (`[` and `]`) or an object value (`{` and `}`).
|
||||
|
||||
The "introduction" is equivalent in both cases: the keyword `for` followed by
|
||||
either one or two identifiers separated by a comma which define the temporary
|
||||
variable names used for iteration, followed by the keyword `in` and then
|
||||
an expression that must evaluate to a value that can be iterated. The
|
||||
introduction is then terminated by the colon (`:`) symbol.
|
||||
|
||||
If only one identifier is provided, it is the name of a variable that will
|
||||
be temporarily assigned the value of each element during iteration. If both
|
||||
are provided, the first is the key and the second is the value.
|
||||
|
||||
Tuple, object, list, map, and set types are iterable. The type of collection
|
||||
used defines how the key and value variables are populated:
|
||||
|
||||
- For tuple and list types, the _key_ is the zero-based index into the
|
||||
sequence for each element, and the _value_ is the element value. The
|
||||
elements are visited in index order.
|
||||
- For object and map types, the _key_ is the string attribute name or element
|
||||
key, and the _value_ is the attribute or element value. The elements are
|
||||
visited in the order defined by a lexicographic sort of the attribute names
|
||||
or keys.
|
||||
- For set types, the _key_ and _value_ are both the element value. The elements
|
||||
are visited in an undefined but consistent order.
|
||||
|
||||
The expression after the colon and (in the case of object `for`) the expression
|
||||
after the `=>` are both evaluated once for each element of the source
|
||||
collection, in a local scope that defines the key and value variable names
|
||||
specified.
|
||||
|
||||
The results of evaluating these expressions for each input element are used
|
||||
to populate an element in the new collection. In the case of tuple `for`, the
|
||||
single expression becomes an element, appending values to the tuple in visit
|
||||
order. In the case of object `for`, the pair of expressions is used as an
|
||||
attribute name and value respectively, creating an element in the resulting
|
||||
object.
|
||||
|
||||
In the case of object `for`, it is an error if two input elements produce
|
||||
the same result from the attribute name expression, since duplicate
|
||||
attributes are not possible. If the ellipsis symbol (`...`) appears
|
||||
immediately after the value expression, this activates the grouping mode in
|
||||
which each value in the resulting object is a _tuple_ of all of the values
|
||||
that were produced against each distinct key.
|
||||
|
||||
- `[for v in ["a", "b"]: v]` returns `["a", "b"]`.
|
||||
- `[for i, v in ["a", "b"]: i]` returns `[0, 1]`.
|
||||
- `{for i, v in ["a", "b"]: v => i}` returns `{a = 0, b = 1}`.
|
||||
- `{for i, v in ["a", "a", "b"]: k => v}` produces an error, because attribute
|
||||
`a` is defined twice.
|
||||
- `{for i, v in ["a", "a", "b"]: v => i...}` returns `{a = [0, 1], b = [2]}`.
|
||||
|
||||
If the `if` keyword is used after the element expression(s), it applies an
|
||||
additional predicate that can be used to conditionally filter elements from
|
||||
the source collection from consideration. The expression following `if` is
|
||||
evaluated once for each source element, in the same scope used for the
|
||||
element expression(s). It must evaluate to a boolean value; if `true`, the
|
||||
element will be evaluated as normal, while if `false` the element will be
|
||||
skipped.
|
||||
|
||||
- `[for i, v in ["a", "b", "c"]: v if i < 2]` returns `["a", "b"]`.
|
||||
|
||||
If the collection value, element expression(s) or condition expression return
|
||||
unknown values that are otherwise type-valid, the result is a value of the
|
||||
dynamic pseudo-type.
|
||||
|
||||
### Index Operator
|
||||
|
||||
The _index_ operator returns the value of a single element of a collection
|
||||
value. It is a postfix operator and can be applied to any value that has
|
||||
a tuple, object, map, or list type.
|
||||
|
||||
```ebnf
|
||||
Index = "[" Expression "]";
|
||||
```
|
||||
|
||||
The expression delimited by the brackets is the _key_ by which an element
|
||||
will be looked up.
|
||||
|
||||
If the index operator is applied to a value of tuple or list type, the
|
||||
key expression must be an non-negative integer number representing the
|
||||
zero-based element index to access. If applied to a value of object or map
|
||||
type, the key expression must be a string representing the attribute name
|
||||
or element key. If the given key value is not of the appropriate type, a
|
||||
conversion is attempted using the conversion rules from the HCL
|
||||
syntax-agnostic information model.
|
||||
|
||||
An error is produced if the given key expression does not correspond to
|
||||
an element in the collection, either because it is of an unconvertable type,
|
||||
because it is outside the range of elements for a tuple or list, or because
|
||||
the given attribute or key does not exist.
|
||||
|
||||
If either the collection or the key are an unknown value of an
|
||||
otherwise-suitable type, the return value is an unknown value whose type
|
||||
matches what type would be returned given known values, or a value of the
|
||||
dynamic pseudo-type if type information alone cannot determine a suitable
|
||||
return type.
|
||||
|
||||
Within the brackets that delimit the index key, newline sequences are ignored
|
||||
as whitespace.
|
||||
|
||||
The HCL native syntax also includes a _legacy_ index operator that exists
|
||||
only for compatibility with the precursor language HIL:
|
||||
|
||||
```ebnf
|
||||
LegacyIndex = '.' digit+
|
||||
```
|
||||
|
||||
This legacy index operator must be supported by parser for compatibility but
|
||||
should not be used in new configurations. This allows an attribute-access-like
|
||||
syntax for indexing, must still be interpreted as an index operation rather
|
||||
than attribute access.
|
||||
|
||||
The legacy syntax does not support chaining of index operations, like
|
||||
`foo.0.0.bar`, because the interpretation of `0.0` as a number literal token
|
||||
takes priority and thus renders the resulting sequence invalid.
|
||||
|
||||
### Attribute Access Operator
|
||||
|
||||
The _attribute access_ operator returns the value of a single attribute in
|
||||
an object value. It is a postfix operator and can be applied to any value
|
||||
that has an object type.
|
||||
|
||||
```ebnf
|
||||
GetAttr = "." Identifier;
|
||||
```
|
||||
|
||||
The given identifier is interpreted as the name of the attribute to access.
|
||||
An error is produced if the object to which the operator is applied does not
|
||||
have an attribute with the given name.
|
||||
|
||||
If the object is an unknown value of a type that has the attribute named, the
|
||||
result is an unknown value of the attribute's type.
|
||||
|
||||
### Splat Operators
|
||||
|
||||
The _splat operators_ allow convenient access to attributes or elements of
|
||||
elements in a tuple, list, or set value.
|
||||
|
||||
There are two kinds of "splat" operator:
|
||||
|
||||
- The _attribute-only_ splat operator supports only attribute lookups into
|
||||
the elements from a list, but supports an arbitrary number of them.
|
||||
|
||||
- The _full_ splat operator additionally supports indexing into the elements
|
||||
from a list, and allows any combination of attribute access and index
|
||||
operations.
|
||||
|
||||
```ebnf
|
||||
Splat = attrSplat | fullSplat;
|
||||
attrSplat = "." "*" GetAttr*;
|
||||
fullSplat = "[" "*" "]" (GetAttr | Index)*;
|
||||
```
|
||||
|
||||
The splat operators can be thought of as shorthands for common operations that
|
||||
could otherwise be performed using _for expressions_:
|
||||
|
||||
- `tuple.*.foo.bar[0]` is approximately equivalent to
|
||||
`[for v in tuple: v.foo.bar][0]`.
|
||||
- `tuple[*].foo.bar[0]` is approximately equivalent to
|
||||
`[for v in tuple: v.foo.bar[0]]`
|
||||
|
||||
Note the difference in how the trailing index operator is interpreted in
|
||||
each case. This different interpretation is the key difference between the
|
||||
_attribute-only_ and _full_ splat operators.
|
||||
|
||||
Splat operators have one additional behavior compared to the equivalent
|
||||
_for expressions_ shown above: if a splat operator is applied to a value that
|
||||
is _not_ of tuple, list, or set type, the value is coerced automatically into
|
||||
a single-value list of the value type:
|
||||
|
||||
- `any_object.*.id` is equivalent to `[any_object.id]`, assuming that `any_object`
|
||||
is a single object.
|
||||
- `any_number.*` is equivalent to `[any_number]`, assuming that `any_number`
|
||||
is a single number.
|
||||
|
||||
If applied to a null value that is not tuple, list, or set, the result is always
|
||||
an empty tuple, which allows conveniently converting a possibly-null scalar
|
||||
value into a tuple of zero or one elements. It is illegal to apply a splat
|
||||
operator to a null value of tuple, list, or set type.
|
||||
|
||||
### Operations
|
||||
|
||||
Operations apply a particular operator to either one or two expression terms.
|
||||
|
||||
```ebnf
|
||||
Operation = unaryOp | binaryOp;
|
||||
unaryOp = ("-" | "!") ExprTerm;
|
||||
binaryOp = ExprTerm binaryOperator ExprTerm;
|
||||
binaryOperator = compareOperator | arithmeticOperator | logicOperator;
|
||||
compareOperator = "==" | "!=" | "<" | ">" | "<=" | ">=";
|
||||
arithmeticOperator = "+" | "-" | "*" | "/" | "%";
|
||||
logicOperator = "&&" | "||" | "!";
|
||||
```
|
||||
|
||||
The unary operators have the highest precedence.
|
||||
|
||||
The binary operators are grouped into the following precedence levels:
|
||||
|
||||
```
|
||||
Level Operators
|
||||
6 * / %
|
||||
5 + -
|
||||
4 > >= < <=
|
||||
3 == !=
|
||||
2 &&
|
||||
1 ||
|
||||
```
|
||||
|
||||
Higher values of "level" bind tighter. Operators within the same precedence
|
||||
level have left-to-right associativity. For example, `x / y * z` is equivalent
|
||||
to `(x / y) * z`.
|
||||
|
||||
### Comparison Operators
|
||||
|
||||
Comparison operators always produce boolean values, as a result of testing
|
||||
the relationship between two values.
|
||||
|
||||
The two equality operators apply to values of any type:
|
||||
|
||||
```
|
||||
a == b equal
|
||||
a != b not equal
|
||||
```
|
||||
|
||||
Two values are equal if the are of identical types and their values are
|
||||
equal as defined in the HCL syntax-agnostic information model. The equality
|
||||
operators are commutative and opposite, such that `(a == b) == !(a != b)`
|
||||
and `(a == b) == (b == a)` for all values `a` and `b`.
|
||||
|
||||
The four numeric comparison operators apply only to numbers:
|
||||
|
||||
```
|
||||
a < b less than
|
||||
a <= b less than or equal to
|
||||
a > b greater than
|
||||
a >= b greater than or equal to
|
||||
```
|
||||
|
||||
If either operand of a comparison operator is a correctly-typed unknown value
|
||||
or a value of the dynamic pseudo-type, the result is an unknown boolean.
|
||||
|
||||
### Arithmetic Operators
|
||||
|
||||
Arithmetic operators apply only to number values and always produce number
|
||||
values as results.
|
||||
|
||||
```
|
||||
a + b sum (addition)
|
||||
a - b difference (subtraction)
|
||||
a * b product (multiplication)
|
||||
a / b quotient (division)
|
||||
a % b remainder (modulo)
|
||||
-a negation
|
||||
```
|
||||
|
||||
Arithmetic operations are considered to be performed in an arbitrary-precision
|
||||
number space.
|
||||
|
||||
If either operand of an arithmetic operator is an unknown number or a value
|
||||
of the dynamic pseudo-type, the result is an unknown number.
|
||||
|
||||
### Logic Operators
|
||||
|
||||
Logic operators apply only to boolean values and always produce boolean values
|
||||
as results.
|
||||
|
||||
```
|
||||
a && b logical AND
|
||||
a || b logical OR
|
||||
!a logical NOT
|
||||
```
|
||||
|
||||
If either operand of a logic operator is an unknown bool value or a value
|
||||
of the dynamic pseudo-type, the result is an unknown bool value.
|
||||
|
||||
### Conditional Operator
|
||||
|
||||
The conditional operator allows selecting from one of two expressions based on
|
||||
the outcome of a boolean expression.
|
||||
|
||||
```ebnf
|
||||
Conditional = Expression "?" Expression ":" Expression;
|
||||
```
|
||||
|
||||
The first expression is the _predicate_, which is evaluated and must produce
|
||||
a boolean result. If the predicate value is `true`, the result of the second
|
||||
expression is the result of the conditional. If the predicate value is
|
||||
`false`, the result of the third expression is the result of the conditional.
|
||||
|
||||
The second and third expressions must be of the same type or must be able to
|
||||
unify into a common type using the type unification rules defined in the
|
||||
HCL syntax-agnostic information model. This unified type is the result type
|
||||
of the conditional, with both expressions converted as necessary to the
|
||||
unified type.
|
||||
|
||||
If the predicate is an unknown boolean value or a value of the dynamic
|
||||
pseudo-type then the result is an unknown value of the unified type of the
|
||||
other two expressions.
|
||||
|
||||
If either the second or third expressions produce errors when evaluated,
|
||||
these errors are passed through only if the erroneous expression is selected.
|
||||
This allows for expressions such as
|
||||
`length(some_list) > 0 ? some_list[0] : default` (given some suitable `length`
|
||||
function) without producing an error when the predicate is `false`.
|
||||
|
||||
## Templates
|
||||
|
||||
The template sub-language is used within template expressions to concisely
|
||||
combine strings and other values to produce other strings. It can also be
|
||||
used in isolation as a standalone template language.
|
||||
|
||||
```ebnf
|
||||
Template = (
|
||||
TemplateLiteral |
|
||||
TemplateInterpolation |
|
||||
TemplateDirective
|
||||
)*
|
||||
TemplateDirective = TemplateIf | TemplateFor;
|
||||
```
|
||||
|
||||
A template behaves like an expression that always returns a string value.
|
||||
The different elements of the template are evaluated and combined into a
|
||||
single string to return. If any of the elements produce an unknown string
|
||||
or a value of the dynamic pseudo-type, the result is an unknown string.
|
||||
|
||||
An important use-case for standalone templates is to enable the use of
|
||||
expressions in alternative HCL syntaxes where a native expression grammar is
|
||||
not available. For example, the HCL JSON profile treats the values of JSON
|
||||
strings as standalone templates when attributes are evaluated in expression
|
||||
mode.
|
||||
|
||||
### Template Literals
|
||||
|
||||
A template literal is a literal sequence of characters to include in the
|
||||
resulting string. When the template sub-language is used standalone, a
|
||||
template literal can contain any unicode character, with the exception
|
||||
of the sequences that introduce interpolations and directives, and for the
|
||||
sequences that escape those introductions.
|
||||
|
||||
The interpolation and directive introductions are escaped by doubling their
|
||||
leading characters. The `${` sequence is escaped as `$${` and the `%{`
|
||||
sequence is escaped as `%%{`.
|
||||
|
||||
When the template sub-language is embedded in the expression language via
|
||||
_template expressions_, additional constraints and transforms are applied to
|
||||
template literals as described in the definition of template expressions.
|
||||
|
||||
The value of a template literal can be modified by _strip markers_ in any
|
||||
interpolations or directives that are adjacent to it. A strip marker is
|
||||
a tilde (`~`) placed immediately after the opening `{` or before the closing
|
||||
`}` of a template sequence:
|
||||
|
||||
- `hello ${~ "world" }` produces `"helloworld"`.
|
||||
- `%{ if true ~} hello %{~ endif }` produces `"hello"`.
|
||||
|
||||
When a strip marker is present, any spaces adjacent to it in the corresponding
|
||||
string literal (if any) are removed before producing the final value. Space
|
||||
characters are interpreted as per Unicode's definition.
|
||||
|
||||
Stripping is done at syntax level rather than value level. Values returned
|
||||
by interpolations or directives are not subject to stripping:
|
||||
|
||||
- `${"hello" ~}${" world"}` produces `"hello world"`, and not `"helloworld"`,
|
||||
because the space is not in a template literal directly adjacent to the
|
||||
strip marker.
|
||||
|
||||
### Template Interpolations
|
||||
|
||||
An _interpolation sequence_ evaluates an expression (written in the
|
||||
expression sub-language), converts the result to a string value, and
|
||||
replaces itself with the resulting string.
|
||||
|
||||
```ebnf
|
||||
TemplateInterpolation = ("${" | "${~") Expression ("}" | "~}";
|
||||
```
|
||||
|
||||
If the expression result cannot be converted to a string, an error is
|
||||
produced.
|
||||
|
||||
### Template If Directive
|
||||
|
||||
The template `if` directive is the template equivalent of the
|
||||
_conditional expression_, allowing selection of one of two sub-templates based
|
||||
on the value of a predicate expression.
|
||||
|
||||
```ebnf
|
||||
TemplateIf = (
|
||||
("%{" | "%{~") "if" Expression ("}" | "~}")
|
||||
Template
|
||||
(
|
||||
("%{" | "%{~") "else" ("}" | "~}")
|
||||
Template
|
||||
)?
|
||||
("%{" | "%{~") "endif" ("}" | "~}")
|
||||
);
|
||||
```
|
||||
|
||||
The evaluation of the `if` directive is equivalent to the conditional
|
||||
expression, with the following exceptions:
|
||||
|
||||
- The two sub-templates always produce strings, and thus the result value is
|
||||
also always a string.
|
||||
- The `else` clause may be omitted, in which case the conditional's third
|
||||
expression result is implied to be the empty string.
|
||||
|
||||
### Template For Directive
|
||||
|
||||
The template `for` directive is the template equivalent of the _for expression_,
|
||||
producing zero or more copies of its sub-template based on the elements of
|
||||
a collection.
|
||||
|
||||
```ebnf
|
||||
TemplateFor = (
|
||||
("%{" | "%{~") "for" Identifier ("," Identifier) "in" Expression ("}" | "~}")
|
||||
Template
|
||||
("%{" | "%{~") "endfor" ("}" | "~}")
|
||||
);
|
||||
```
|
||||
|
||||
The evaluation of the `for` directive is equivalent to the _for expression_
|
||||
when producing a tuple, with the following exceptions:
|
||||
|
||||
- The sub-template always produces a string.
|
||||
- There is no equivalent of the "if" clause on the for expression.
|
||||
- The elements of the resulting tuple are all converted to strings and
|
||||
concatenated to produce a flat string result.
|
||||
|
||||
### Template Interpolation Unwrapping
|
||||
|
||||
As a special case, a template that consists only of a single interpolation,
|
||||
with no surrounding literals, directives or other interpolations, is
|
||||
"unwrapped". In this case, the result of the interpolation expression is
|
||||
returned verbatim, without conversion to string.
|
||||
|
||||
This special case exists primarily to enable the native template language
|
||||
to be used inside strings in alternative HCL syntaxes that lack a first-class
|
||||
template or expression syntax. Unwrapping allows arbitrary expressions to be
|
||||
used to populate attributes when strings in such languages are interpreted
|
||||
as templates.
|
||||
|
||||
- `${true}` produces the boolean value `true`
|
||||
- `${"${true}"}` produces the boolean value `true`, because both the inner
|
||||
and outer interpolations are subject to unwrapping.
|
||||
- `hello ${true}` produces the string `"hello true"`
|
||||
- `${""}${true}` produces the string `"true"` because there are two
|
||||
interpolation sequences, even though one produces an empty result.
|
||||
- `%{ for v in [true] }${v}%{ endif }` produces the string `true` because
|
||||
the presence of the `for` directive circumvents the unwrapping even though
|
||||
the final result is a single value.
|
||||
|
||||
In some contexts this unwrapping behavior may be circumvented by the calling
|
||||
application, by converting the final template result to string. This is
|
||||
necessary, for example, if a standalone template is being used to produce
|
||||
the direct contents of a file, since the result in that case must always be a
|
||||
string.
|
||||
|
||||
## Static Analysis
|
||||
|
||||
The HCL static analysis operations are implemented for some expression types
|
||||
in the native syntax, as described in the following sections.
|
||||
|
||||
A goal for static analysis of the native syntax is for the interpretation to
|
||||
be as consistent as possible with the dynamic evaluation interpretation of
|
||||
the given expression, though some deviations are intentionally made in order
|
||||
to maximize the potential for analysis.
|
||||
|
||||
### Static List
|
||||
|
||||
The tuple construction syntax can be interpreted as a static list. All of
|
||||
the expression elements given are returned as the static list elements,
|
||||
with no further interpretation.
|
||||
|
||||
### Static Map
|
||||
|
||||
The object construction syntax can be interpreted as a static map. All of the
|
||||
key/value pairs given are returned as the static pairs, with no further
|
||||
interpretation.
|
||||
|
||||
The usual requirement that an attribute name be interpretable as a string
|
||||
does not apply to this static analysis, allowing callers to provide map-like
|
||||
constructs with different key types by building on the map syntax.
|
||||
|
||||
### Static Call
|
||||
|
||||
The function call syntax can be interpreted as a static call. The called
|
||||
function name is returned verbatim and the given argument expressions are
|
||||
returned as the static arguments, with no further interpretation.
|
||||
|
||||
### Static Traversal
|
||||
|
||||
A variable expression and any attached attribute access operations and
|
||||
constant index operations can be interpreted as a static traversal.
|
||||
|
||||
The keywords `true`, `false` and `null` can also be interpreted as
|
||||
static traversals, behaving as if they were references to variables of those
|
||||
names, to allow callers to redefine the meaning of those keywords in certain
|
||||
contexts.
|
394
vendor/github.com/hashicorp/hcl/v2/hclsyntax/structure.go
generated
vendored
Normal file
394
vendor/github.com/hashicorp/hcl/v2/hclsyntax/structure.go
generated
vendored
Normal file
@ -0,0 +1,394 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// AsHCLBlock returns the block data expressed as a *hcl.Block.
|
||||
func (b *Block) AsHCLBlock() *hcl.Block {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
lastHeaderRange := b.TypeRange
|
||||
if len(b.LabelRanges) > 0 {
|
||||
lastHeaderRange = b.LabelRanges[len(b.LabelRanges)-1]
|
||||
}
|
||||
|
||||
return &hcl.Block{
|
||||
Type: b.Type,
|
||||
Labels: b.Labels,
|
||||
Body: b.Body,
|
||||
|
||||
DefRange: hcl.RangeBetween(b.TypeRange, lastHeaderRange),
|
||||
TypeRange: b.TypeRange,
|
||||
LabelRanges: b.LabelRanges,
|
||||
}
|
||||
}
|
||||
|
||||
// Body is the implementation of hcl.Body for the HCL native syntax.
|
||||
type Body struct {
|
||||
Attributes Attributes
|
||||
Blocks Blocks
|
||||
|
||||
// These are used with PartialContent to produce a "remaining items"
|
||||
// body to return. They are nil on all bodies fresh out of the parser.
|
||||
hiddenAttrs map[string]struct{}
|
||||
hiddenBlocks map[string]struct{}
|
||||
|
||||
SrcRange hcl.Range
|
||||
EndRange hcl.Range // Final token of the body, for reporting missing items
|
||||
}
|
||||
|
||||
// Assert that *Body implements hcl.Body
|
||||
var assertBodyImplBody hcl.Body = &Body{}
|
||||
|
||||
func (b *Body) walkChildNodes(w internalWalkFunc) {
|
||||
w(b.Attributes)
|
||||
w(b.Blocks)
|
||||
}
|
||||
|
||||
func (b *Body) Range() hcl.Range {
|
||||
return b.SrcRange
|
||||
}
|
||||
|
||||
func (b *Body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
|
||||
content, remainHCL, diags := b.PartialContent(schema)
|
||||
|
||||
// No we'll see if anything actually remains, to produce errors about
|
||||
// extraneous items.
|
||||
remain := remainHCL.(*Body)
|
||||
|
||||
for name, attr := range b.Attributes {
|
||||
if _, hidden := remain.hiddenAttrs[name]; !hidden {
|
||||
var suggestions []string
|
||||
for _, attrS := range schema.Attributes {
|
||||
if _, defined := content.Attributes[attrS.Name]; defined {
|
||||
continue
|
||||
}
|
||||
suggestions = append(suggestions, attrS.Name)
|
||||
}
|
||||
suggestion := nameSuggestion(name, suggestions)
|
||||
if suggestion != "" {
|
||||
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
|
||||
} else {
|
||||
// Is there a block of the same name?
|
||||
for _, blockS := range schema.Blocks {
|
||||
if blockS.Type == name {
|
||||
suggestion = fmt.Sprintf(" Did you mean to define a block of type %q?", name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported argument",
|
||||
Detail: fmt.Sprintf("An argument named %q is not expected here.%s", name, suggestion),
|
||||
Subject: &attr.NameRange,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, block := range b.Blocks {
|
||||
blockTy := block.Type
|
||||
if _, hidden := remain.hiddenBlocks[blockTy]; !hidden {
|
||||
var suggestions []string
|
||||
for _, blockS := range schema.Blocks {
|
||||
suggestions = append(suggestions, blockS.Type)
|
||||
}
|
||||
suggestion := nameSuggestion(blockTy, suggestions)
|
||||
if suggestion != "" {
|
||||
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
|
||||
} else {
|
||||
// Is there an attribute of the same name?
|
||||
for _, attrS := range schema.Attributes {
|
||||
if attrS.Name == blockTy {
|
||||
suggestion = fmt.Sprintf(" Did you mean to define argument %q? If so, use the equals sign to assign it a value.", blockTy)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported block type",
|
||||
Detail: fmt.Sprintf("Blocks of type %q are not expected here.%s", blockTy, suggestion),
|
||||
Subject: &block.TypeRange,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return content, diags
|
||||
}
|
||||
|
||||
func (b *Body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
|
||||
attrs := make(hcl.Attributes)
|
||||
var blocks hcl.Blocks
|
||||
var diags hcl.Diagnostics
|
||||
hiddenAttrs := make(map[string]struct{})
|
||||
hiddenBlocks := make(map[string]struct{})
|
||||
|
||||
if b.hiddenAttrs != nil {
|
||||
for k, v := range b.hiddenAttrs {
|
||||
hiddenAttrs[k] = v
|
||||
}
|
||||
}
|
||||
if b.hiddenBlocks != nil {
|
||||
for k, v := range b.hiddenBlocks {
|
||||
hiddenBlocks[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
for _, attrS := range schema.Attributes {
|
||||
name := attrS.Name
|
||||
attr, exists := b.Attributes[name]
|
||||
_, hidden := hiddenAttrs[name]
|
||||
if hidden || !exists {
|
||||
if attrS.Required {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing required argument",
|
||||
Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name),
|
||||
Subject: b.MissingItemRange().Ptr(),
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
hiddenAttrs[name] = struct{}{}
|
||||
attrs[name] = attr.AsHCLAttribute()
|
||||
}
|
||||
|
||||
blocksWanted := make(map[string]hcl.BlockHeaderSchema)
|
||||
for _, blockS := range schema.Blocks {
|
||||
blocksWanted[blockS.Type] = blockS
|
||||
}
|
||||
|
||||
for _, block := range b.Blocks {
|
||||
if _, hidden := hiddenBlocks[block.Type]; hidden {
|
||||
continue
|
||||
}
|
||||
blockS, wanted := blocksWanted[block.Type]
|
||||
if !wanted {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(block.Labels) > len(blockS.LabelNames) {
|
||||
name := block.Type
|
||||
if len(blockS.LabelNames) == 0 {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Extraneous label for %s", name),
|
||||
Detail: fmt.Sprintf(
|
||||
"No labels are expected for %s blocks.", name,
|
||||
),
|
||||
Subject: block.LabelRanges[0].Ptr(),
|
||||
Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
|
||||
})
|
||||
} else {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.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: block.LabelRanges[len(blockS.LabelNames)].Ptr(),
|
||||
Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if len(block.Labels) < len(blockS.LabelNames) {
|
||||
name := block.Type
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Missing %s for %s", blockS.LabelNames[len(block.Labels)], name),
|
||||
Detail: fmt.Sprintf(
|
||||
"All %s blocks must have %d labels (%s).",
|
||||
name, len(blockS.LabelNames), strings.Join(blockS.LabelNames, ", "),
|
||||
),
|
||||
Subject: &block.OpenBraceRange,
|
||||
Context: hcl.RangeBetween(block.TypeRange, block.OpenBraceRange).Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
blocks = append(blocks, block.AsHCLBlock())
|
||||
}
|
||||
|
||||
// We hide blocks only after we've processed all of them, since otherwise
|
||||
// we can't process more than one of the same type.
|
||||
for _, blockS := range schema.Blocks {
|
||||
hiddenBlocks[blockS.Type] = struct{}{}
|
||||
}
|
||||
|
||||
remain := &Body{
|
||||
Attributes: b.Attributes,
|
||||
Blocks: b.Blocks,
|
||||
|
||||
hiddenAttrs: hiddenAttrs,
|
||||
hiddenBlocks: hiddenBlocks,
|
||||
|
||||
SrcRange: b.SrcRange,
|
||||
EndRange: b.EndRange,
|
||||
}
|
||||
|
||||
return &hcl.BodyContent{
|
||||
Attributes: attrs,
|
||||
Blocks: blocks,
|
||||
|
||||
MissingItemRange: b.MissingItemRange(),
|
||||
}, remain, diags
|
||||
}
|
||||
|
||||
func (b *Body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
|
||||
attrs := make(hcl.Attributes)
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
if len(b.Blocks) > 0 {
|
||||
example := b.Blocks[0]
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: fmt.Sprintf("Unexpected %q block", example.Type),
|
||||
Detail: "Blocks are not allowed here.",
|
||||
Subject: &example.TypeRange,
|
||||
})
|
||||
// we will continue processing anyway, and return the attributes
|
||||
// we are able to find so that certain analyses can still be done
|
||||
// in the face of errors.
|
||||
}
|
||||
|
||||
if b.Attributes == nil {
|
||||
return attrs, diags
|
||||
}
|
||||
|
||||
for name, attr := range b.Attributes {
|
||||
if _, hidden := b.hiddenAttrs[name]; hidden {
|
||||
continue
|
||||
}
|
||||
attrs[name] = attr.AsHCLAttribute()
|
||||
}
|
||||
|
||||
return attrs, diags
|
||||
}
|
||||
|
||||
func (b *Body) MissingItemRange() hcl.Range {
|
||||
return hcl.Range{
|
||||
Filename: b.SrcRange.Filename,
|
||||
Start: b.SrcRange.Start,
|
||||
End: b.SrcRange.Start,
|
||||
}
|
||||
}
|
||||
|
||||
// Attributes is the collection of attribute definitions within a body.
|
||||
type Attributes map[string]*Attribute
|
||||
|
||||
func (a Attributes) walkChildNodes(w internalWalkFunc) {
|
||||
for _, attr := range a {
|
||||
w(attr)
|
||||
}
|
||||
}
|
||||
|
||||
// Range returns the range of some arbitrary point within the set of
|
||||
// attributes, or an invalid range if there are no attributes.
|
||||
//
|
||||
// This is provided only to complete the Node interface, but has no practical
|
||||
// use.
|
||||
func (a Attributes) Range() hcl.Range {
|
||||
// An attributes doesn't really have a useful range to report, since
|
||||
// it's just a grouping construct. So we'll arbitrarily take the
|
||||
// range of one of the attributes, or produce an invalid range if we have
|
||||
// none. In practice, there's little reason to ask for the range of
|
||||
// an Attributes.
|
||||
for _, attr := range a {
|
||||
return attr.Range()
|
||||
}
|
||||
return hcl.Range{
|
||||
Filename: "<unknown>",
|
||||
}
|
||||
}
|
||||
|
||||
// Attribute represents a single attribute definition within a body.
|
||||
type Attribute struct {
|
||||
Name string
|
||||
Expr Expression
|
||||
|
||||
SrcRange hcl.Range
|
||||
NameRange hcl.Range
|
||||
EqualsRange hcl.Range
|
||||
}
|
||||
|
||||
func (a *Attribute) walkChildNodes(w internalWalkFunc) {
|
||||
w(a.Expr)
|
||||
}
|
||||
|
||||
func (a *Attribute) Range() hcl.Range {
|
||||
return a.SrcRange
|
||||
}
|
||||
|
||||
// AsHCLAttribute returns the block data expressed as a *hcl.Attribute.
|
||||
func (a *Attribute) AsHCLAttribute() *hcl.Attribute {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
return &hcl.Attribute{
|
||||
Name: a.Name,
|
||||
Expr: a.Expr,
|
||||
|
||||
Range: a.SrcRange,
|
||||
NameRange: a.NameRange,
|
||||
}
|
||||
}
|
||||
|
||||
// Blocks is the list of nested blocks within a body.
|
||||
type Blocks []*Block
|
||||
|
||||
func (bs Blocks) walkChildNodes(w internalWalkFunc) {
|
||||
for _, block := range bs {
|
||||
w(block)
|
||||
}
|
||||
}
|
||||
|
||||
// Range returns the range of some arbitrary point within the list of
|
||||
// blocks, or an invalid range if there are no blocks.
|
||||
//
|
||||
// This is provided only to complete the Node interface, but has no practical
|
||||
// use.
|
||||
func (bs Blocks) Range() hcl.Range {
|
||||
if len(bs) > 0 {
|
||||
return bs[0].Range()
|
||||
}
|
||||
return hcl.Range{
|
||||
Filename: "<unknown>",
|
||||
}
|
||||
}
|
||||
|
||||
// Block represents a nested block structure
|
||||
type Block struct {
|
||||
Type string
|
||||
Labels []string
|
||||
Body *Body
|
||||
|
||||
TypeRange hcl.Range
|
||||
LabelRanges []hcl.Range
|
||||
OpenBraceRange hcl.Range
|
||||
CloseBraceRange hcl.Range
|
||||
}
|
||||
|
||||
func (b *Block) walkChildNodes(w internalWalkFunc) {
|
||||
w(b.Body)
|
||||
}
|
||||
|
||||
func (b *Block) Range() hcl.Range {
|
||||
return hcl.RangeBetween(b.TypeRange, b.CloseBraceRange)
|
||||
}
|
||||
|
||||
func (b *Block) DefRange() hcl.Range {
|
||||
return hcl.RangeBetween(b.TypeRange, b.OpenBraceRange)
|
||||
}
|
118
vendor/github.com/hashicorp/hcl/v2/hclsyntax/structure_at_pos.go
generated
vendored
Normal file
118
vendor/github.com/hashicorp/hcl/v2/hclsyntax/structure_at_pos.go
generated
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// The methods in this file are all optional extension methods that serve to
|
||||
// implement the methods of the same name on *hcl.File when its root body
|
||||
// is provided by this package.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// BlocksAtPos implements the method of the same name for an *hcl.File that
|
||||
// is backed by a *Body.
|
||||
func (b *Body) BlocksAtPos(pos hcl.Pos) []*hcl.Block {
|
||||
list, _ := b.blocksAtPos(pos, true)
|
||||
return list
|
||||
}
|
||||
|
||||
// InnermostBlockAtPos implements the method of the same name for an *hcl.File
|
||||
// that is backed by a *Body.
|
||||
func (b *Body) InnermostBlockAtPos(pos hcl.Pos) *hcl.Block {
|
||||
_, innermost := b.blocksAtPos(pos, false)
|
||||
return innermost.AsHCLBlock()
|
||||
}
|
||||
|
||||
// OutermostBlockAtPos implements the method of the same name for an *hcl.File
|
||||
// that is backed by a *Body.
|
||||
func (b *Body) OutermostBlockAtPos(pos hcl.Pos) *hcl.Block {
|
||||
return b.outermostBlockAtPos(pos).AsHCLBlock()
|
||||
}
|
||||
|
||||
// blocksAtPos is the internal engine of both BlocksAtPos and
|
||||
// InnermostBlockAtPos, which both need to do the same logic but return a
|
||||
// differently-shaped result.
|
||||
//
|
||||
// list is nil if makeList is false, avoiding an allocation. Innermost is
|
||||
// always set, and if the returned list is non-nil it will always match the
|
||||
// final element from that list.
|
||||
func (b *Body) blocksAtPos(pos hcl.Pos, makeList bool) (list []*hcl.Block, innermost *Block) {
|
||||
current := b
|
||||
|
||||
Blocks:
|
||||
for current != nil {
|
||||
for _, block := range current.Blocks {
|
||||
wholeRange := hcl.RangeBetween(block.TypeRange, block.CloseBraceRange)
|
||||
if wholeRange.ContainsPos(pos) {
|
||||
innermost = block
|
||||
if makeList {
|
||||
list = append(list, innermost.AsHCLBlock())
|
||||
}
|
||||
current = block.Body
|
||||
continue Blocks
|
||||
}
|
||||
}
|
||||
|
||||
// If we fall out here then none of the current body's nested blocks
|
||||
// contain the position we are looking for, and so we're done.
|
||||
break
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// outermostBlockAtPos is the internal version of OutermostBlockAtPos that
|
||||
// returns a hclsyntax.Block rather than an hcl.Block, allowing for further
|
||||
// analysis if necessary.
|
||||
func (b *Body) outermostBlockAtPos(pos hcl.Pos) *Block {
|
||||
// This is similar to blocksAtPos, but simpler because we know it only
|
||||
// ever needs to search the first level of nested blocks.
|
||||
|
||||
for _, block := range b.Blocks {
|
||||
wholeRange := hcl.RangeBetween(block.TypeRange, block.CloseBraceRange)
|
||||
if wholeRange.ContainsPos(pos) {
|
||||
return block
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AttributeAtPos implements the method of the same name for an *hcl.File
|
||||
// that is backed by a *Body.
|
||||
func (b *Body) AttributeAtPos(pos hcl.Pos) *hcl.Attribute {
|
||||
return b.attributeAtPos(pos).AsHCLAttribute()
|
||||
}
|
||||
|
||||
// attributeAtPos is the internal version of AttributeAtPos that returns a
|
||||
// hclsyntax.Block rather than an hcl.Block, allowing for further analysis if
|
||||
// necessary.
|
||||
func (b *Body) attributeAtPos(pos hcl.Pos) *Attribute {
|
||||
searchBody := b
|
||||
_, block := b.blocksAtPos(pos, false)
|
||||
if block != nil {
|
||||
searchBody = block.Body
|
||||
}
|
||||
|
||||
for _, attr := range searchBody.Attributes {
|
||||
if attr.SrcRange.ContainsPos(pos) {
|
||||
return attr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// OutermostExprAtPos implements the method of the same name for an *hcl.File
|
||||
// that is backed by a *Body.
|
||||
func (b *Body) OutermostExprAtPos(pos hcl.Pos) hcl.Expression {
|
||||
attr := b.attributeAtPos(pos)
|
||||
if attr == nil {
|
||||
return nil
|
||||
}
|
||||
if !attr.Expr.Range().ContainsPos(pos) {
|
||||
return nil
|
||||
}
|
||||
return attr.Expr
|
||||
}
|
320
vendor/github.com/hashicorp/hcl/v2/hclsyntax/token.go
generated
vendored
Normal file
320
vendor/github.com/hashicorp/hcl/v2/hclsyntax/token.go
generated
vendored
Normal file
@ -0,0 +1,320 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/apparentlymart/go-textseg/v12/textseg"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// Token represents a sequence of bytes from some HCL code that has been
|
||||
// tagged with a type and its range within the source file.
|
||||
type Token struct {
|
||||
Type TokenType
|
||||
Bytes []byte
|
||||
Range hcl.Range
|
||||
}
|
||||
|
||||
// Tokens is a slice of Token.
|
||||
type Tokens []Token
|
||||
|
||||
// TokenType is an enumeration used for the Type field on Token.
|
||||
type TokenType rune
|
||||
|
||||
const (
|
||||
// Single-character tokens are represented by their own character, for
|
||||
// convenience in producing these within the scanner. However, the values
|
||||
// are otherwise arbitrary and just intended to be mnemonic for humans
|
||||
// who might see them in debug output.
|
||||
|
||||
TokenOBrace TokenType = '{'
|
||||
TokenCBrace TokenType = '}'
|
||||
TokenOBrack TokenType = '['
|
||||
TokenCBrack TokenType = ']'
|
||||
TokenOParen TokenType = '('
|
||||
TokenCParen TokenType = ')'
|
||||
TokenOQuote TokenType = '«'
|
||||
TokenCQuote TokenType = '»'
|
||||
TokenOHeredoc TokenType = 'H'
|
||||
TokenCHeredoc TokenType = 'h'
|
||||
|
||||
TokenStar TokenType = '*'
|
||||
TokenSlash TokenType = '/'
|
||||
TokenPlus TokenType = '+'
|
||||
TokenMinus TokenType = '-'
|
||||
TokenPercent TokenType = '%'
|
||||
|
||||
TokenEqual TokenType = '='
|
||||
TokenEqualOp TokenType = '≔'
|
||||
TokenNotEqual TokenType = '≠'
|
||||
TokenLessThan TokenType = '<'
|
||||
TokenLessThanEq TokenType = '≤'
|
||||
TokenGreaterThan TokenType = '>'
|
||||
TokenGreaterThanEq TokenType = '≥'
|
||||
|
||||
TokenAnd TokenType = '∧'
|
||||
TokenOr TokenType = '∨'
|
||||
TokenBang TokenType = '!'
|
||||
|
||||
TokenDot TokenType = '.'
|
||||
TokenComma TokenType = ','
|
||||
|
||||
TokenEllipsis TokenType = '…'
|
||||
TokenFatArrow TokenType = '⇒'
|
||||
|
||||
TokenQuestion TokenType = '?'
|
||||
TokenColon TokenType = ':'
|
||||
|
||||
TokenTemplateInterp TokenType = '∫'
|
||||
TokenTemplateControl TokenType = 'λ'
|
||||
TokenTemplateSeqEnd TokenType = '∎'
|
||||
|
||||
TokenQuotedLit TokenType = 'Q' // might contain backslash escapes
|
||||
TokenStringLit TokenType = 'S' // cannot contain backslash escapes
|
||||
TokenNumberLit TokenType = 'N'
|
||||
TokenIdent TokenType = 'I'
|
||||
|
||||
TokenComment TokenType = 'C'
|
||||
|
||||
TokenNewline TokenType = '\n'
|
||||
TokenEOF TokenType = '␄'
|
||||
|
||||
// The rest are not used in the language but recognized by the scanner so
|
||||
// we can generate good diagnostics in the parser when users try to write
|
||||
// things that might work in other languages they are familiar with, or
|
||||
// simply make incorrect assumptions about the HCL language.
|
||||
|
||||
TokenBitwiseAnd TokenType = '&'
|
||||
TokenBitwiseOr TokenType = '|'
|
||||
TokenBitwiseNot TokenType = '~'
|
||||
TokenBitwiseXor TokenType = '^'
|
||||
TokenStarStar TokenType = '➚'
|
||||
TokenApostrophe TokenType = '\''
|
||||
TokenBacktick TokenType = '`'
|
||||
TokenSemicolon TokenType = ';'
|
||||
TokenTabs TokenType = '␉'
|
||||
TokenInvalid TokenType = '<27>'
|
||||
TokenBadUTF8 TokenType = '💩'
|
||||
TokenQuotedNewline TokenType = ''
|
||||
|
||||
// TokenNil is a placeholder for when a token is required but none is
|
||||
// available, e.g. when reporting errors. The scanner will never produce
|
||||
// this as part of a token stream.
|
||||
TokenNil TokenType = '\x00'
|
||||
)
|
||||
|
||||
func (t TokenType) GoString() string {
|
||||
return fmt.Sprintf("hclsyntax.%s", t.String())
|
||||
}
|
||||
|
||||
type scanMode int
|
||||
|
||||
const (
|
||||
scanNormal scanMode = iota
|
||||
scanTemplate
|
||||
scanIdentOnly
|
||||
)
|
||||
|
||||
type tokenAccum struct {
|
||||
Filename string
|
||||
Bytes []byte
|
||||
Pos hcl.Pos
|
||||
Tokens []Token
|
||||
StartByte int
|
||||
}
|
||||
|
||||
func (f *tokenAccum) emitToken(ty TokenType, startOfs, endOfs int) {
|
||||
// Walk through our buffer to figure out how much we need to adjust
|
||||
// the start pos to get our end pos.
|
||||
|
||||
start := f.Pos
|
||||
start.Column += startOfs + f.StartByte - f.Pos.Byte // Safe because only ASCII spaces can be in the offset
|
||||
start.Byte = startOfs + f.StartByte
|
||||
|
||||
end := start
|
||||
end.Byte = endOfs + f.StartByte
|
||||
b := f.Bytes[startOfs:endOfs]
|
||||
for len(b) > 0 {
|
||||
advance, seq, _ := textseg.ScanGraphemeClusters(b, true)
|
||||
if (len(seq) == 1 && seq[0] == '\n') || (len(seq) == 2 && seq[0] == '\r' && seq[1] == '\n') {
|
||||
end.Line++
|
||||
end.Column = 1
|
||||
} else {
|
||||
end.Column++
|
||||
}
|
||||
b = b[advance:]
|
||||
}
|
||||
|
||||
f.Pos = end
|
||||
|
||||
f.Tokens = append(f.Tokens, Token{
|
||||
Type: ty,
|
||||
Bytes: f.Bytes[startOfs:endOfs],
|
||||
Range: hcl.Range{
|
||||
Filename: f.Filename,
|
||||
Start: start,
|
||||
End: end,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type heredocInProgress struct {
|
||||
Marker []byte
|
||||
StartOfLine bool
|
||||
}
|
||||
|
||||
func tokenOpensFlushHeredoc(tok Token) bool {
|
||||
if tok.Type != TokenOHeredoc {
|
||||
return false
|
||||
}
|
||||
return bytes.HasPrefix(tok.Bytes, []byte{'<', '<', '-'})
|
||||
}
|
||||
|
||||
// checkInvalidTokens does a simple pass across the given tokens and generates
|
||||
// diagnostics for tokens that should _never_ appear in HCL source. This
|
||||
// is intended to avoid the need for the parser to have special support
|
||||
// for them all over.
|
||||
//
|
||||
// Returns a diagnostics with no errors if everything seems acceptable.
|
||||
// Otherwise, returns zero or more error diagnostics, though tries to limit
|
||||
// repetition of the same information.
|
||||
func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
toldBitwise := 0
|
||||
toldExponent := 0
|
||||
toldBacktick := 0
|
||||
toldApostrophe := 0
|
||||
toldSemicolon := 0
|
||||
toldTabs := 0
|
||||
toldBadUTF8 := 0
|
||||
|
||||
for _, tok := range tokens {
|
||||
// copy token so it's safe to point to it
|
||||
tok := tok
|
||||
|
||||
switch tok.Type {
|
||||
case TokenBitwiseAnd, TokenBitwiseOr, TokenBitwiseXor, TokenBitwiseNot:
|
||||
if toldBitwise < 4 {
|
||||
var suggestion string
|
||||
switch tok.Type {
|
||||
case TokenBitwiseAnd:
|
||||
suggestion = " Did you mean boolean AND (\"&&\")?"
|
||||
case TokenBitwiseOr:
|
||||
suggestion = " Did you mean boolean OR (\"&&\")?"
|
||||
case TokenBitwiseNot:
|
||||
suggestion = " Did you mean boolean NOT (\"!\")?"
|
||||
}
|
||||
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported operator",
|
||||
Detail: fmt.Sprintf("Bitwise operators are not supported.%s", suggestion),
|
||||
Subject: &tok.Range,
|
||||
})
|
||||
toldBitwise++
|
||||
}
|
||||
case TokenStarStar:
|
||||
if toldExponent < 1 {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unsupported operator",
|
||||
Detail: "\"**\" is not a supported operator. Exponentiation is not supported as an operator.",
|
||||
Subject: &tok.Range,
|
||||
})
|
||||
|
||||
toldExponent++
|
||||
}
|
||||
case TokenBacktick:
|
||||
// Only report for alternating (even) backticks, so we won't report both start and ends of the same
|
||||
// backtick-quoted string.
|
||||
if (toldBacktick % 2) == 0 {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid character",
|
||||
Detail: "The \"`\" character is not valid. To create a multi-line string, use the \"heredoc\" syntax, like \"<<EOT\".",
|
||||
Subject: &tok.Range,
|
||||
})
|
||||
}
|
||||
if toldBacktick <= 2 {
|
||||
toldBacktick++
|
||||
}
|
||||
case TokenApostrophe:
|
||||
if (toldApostrophe % 2) == 0 {
|
||||
newDiag := &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid character",
|
||||
Detail: "Single quotes are not valid. Use double quotes (\") to enclose strings.",
|
||||
Subject: &tok.Range,
|
||||
}
|
||||
diags = append(diags, newDiag)
|
||||
}
|
||||
if toldApostrophe <= 2 {
|
||||
toldApostrophe++
|
||||
}
|
||||
case TokenSemicolon:
|
||||
if toldSemicolon < 1 {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid character",
|
||||
Detail: "The \";\" character is not valid. Use newlines to separate arguments and blocks, and commas to separate items in collection values.",
|
||||
Subject: &tok.Range,
|
||||
})
|
||||
|
||||
toldSemicolon++
|
||||
}
|
||||
case TokenTabs:
|
||||
if toldTabs < 1 {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid character",
|
||||
Detail: "Tab characters may not be used. The recommended indentation style is two spaces per indent.",
|
||||
Subject: &tok.Range,
|
||||
})
|
||||
|
||||
toldTabs++
|
||||
}
|
||||
case TokenBadUTF8:
|
||||
if toldBadUTF8 < 1 {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid character encoding",
|
||||
Detail: "All input files must be UTF-8 encoded. Ensure that UTF-8 encoding is selected in your editor.",
|
||||
Subject: &tok.Range,
|
||||
})
|
||||
|
||||
toldBadUTF8++
|
||||
}
|
||||
case TokenQuotedNewline:
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid multi-line string",
|
||||
Detail: "Quoted strings may not be split over multiple lines. To produce a multi-line string, either use the \\n escape to represent a newline character or use the \"heredoc\" multi-line template syntax.",
|
||||
Subject: &tok.Range,
|
||||
})
|
||||
case TokenInvalid:
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid character",
|
||||
Detail: "This character is not used within the language.",
|
||||
Subject: &tok.Range,
|
||||
})
|
||||
}
|
||||
}
|
||||
return diags
|
||||
}
|
||||
|
||||
var utf8BOM = []byte{0xef, 0xbb, 0xbf}
|
||||
|
||||
// stripUTF8BOM checks whether the given buffer begins with a UTF-8 byte order
|
||||
// mark (0xEF 0xBB 0xBF) and, if so, returns a truncated slice with the same
|
||||
// backing array but with the BOM skipped.
|
||||
//
|
||||
// If there is no BOM present, the given slice is returned verbatim.
|
||||
func stripUTF8BOM(src []byte) []byte {
|
||||
if bytes.HasPrefix(src, utf8BOM) {
|
||||
return src[3:]
|
||||
}
|
||||
return src
|
||||
}
|
131
vendor/github.com/hashicorp/hcl/v2/hclsyntax/token_type_string.go
generated
vendored
Normal file
131
vendor/github.com/hashicorp/hcl/v2/hclsyntax/token_type_string.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
// Code generated by "stringer -type TokenType -output token_type_string.go"; DO NOT EDIT.
|
||||
|
||||
package hclsyntax
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[TokenOBrace-123]
|
||||
_ = x[TokenCBrace-125]
|
||||
_ = x[TokenOBrack-91]
|
||||
_ = x[TokenCBrack-93]
|
||||
_ = x[TokenOParen-40]
|
||||
_ = x[TokenCParen-41]
|
||||
_ = x[TokenOQuote-171]
|
||||
_ = x[TokenCQuote-187]
|
||||
_ = x[TokenOHeredoc-72]
|
||||
_ = x[TokenCHeredoc-104]
|
||||
_ = x[TokenStar-42]
|
||||
_ = x[TokenSlash-47]
|
||||
_ = x[TokenPlus-43]
|
||||
_ = x[TokenMinus-45]
|
||||
_ = x[TokenPercent-37]
|
||||
_ = x[TokenEqual-61]
|
||||
_ = x[TokenEqualOp-8788]
|
||||
_ = x[TokenNotEqual-8800]
|
||||
_ = x[TokenLessThan-60]
|
||||
_ = x[TokenLessThanEq-8804]
|
||||
_ = x[TokenGreaterThan-62]
|
||||
_ = x[TokenGreaterThanEq-8805]
|
||||
_ = x[TokenAnd-8743]
|
||||
_ = x[TokenOr-8744]
|
||||
_ = x[TokenBang-33]
|
||||
_ = x[TokenDot-46]
|
||||
_ = x[TokenComma-44]
|
||||
_ = x[TokenEllipsis-8230]
|
||||
_ = x[TokenFatArrow-8658]
|
||||
_ = x[TokenQuestion-63]
|
||||
_ = x[TokenColon-58]
|
||||
_ = x[TokenTemplateInterp-8747]
|
||||
_ = x[TokenTemplateControl-955]
|
||||
_ = x[TokenTemplateSeqEnd-8718]
|
||||
_ = x[TokenQuotedLit-81]
|
||||
_ = x[TokenStringLit-83]
|
||||
_ = x[TokenNumberLit-78]
|
||||
_ = x[TokenIdent-73]
|
||||
_ = x[TokenComment-67]
|
||||
_ = x[TokenNewline-10]
|
||||
_ = x[TokenEOF-9220]
|
||||
_ = x[TokenBitwiseAnd-38]
|
||||
_ = x[TokenBitwiseOr-124]
|
||||
_ = x[TokenBitwiseNot-126]
|
||||
_ = x[TokenBitwiseXor-94]
|
||||
_ = x[TokenStarStar-10138]
|
||||
_ = x[TokenApostrophe-39]
|
||||
_ = x[TokenBacktick-96]
|
||||
_ = x[TokenSemicolon-59]
|
||||
_ = x[TokenTabs-9225]
|
||||
_ = x[TokenInvalid-65533]
|
||||
_ = x[TokenBadUTF8-128169]
|
||||
_ = x[TokenQuotedNewline-9252]
|
||||
_ = x[TokenNil-0]
|
||||
}
|
||||
|
||||
const _TokenType_name = "TokenNilTokenNewlineTokenBangTokenPercentTokenBitwiseAndTokenApostropheTokenOParenTokenCParenTokenStarTokenPlusTokenCommaTokenMinusTokenDotTokenSlashTokenColonTokenSemicolonTokenLessThanTokenEqualTokenGreaterThanTokenQuestionTokenCommentTokenOHeredocTokenIdentTokenNumberLitTokenQuotedLitTokenStringLitTokenOBrackTokenCBrackTokenBitwiseXorTokenBacktickTokenCHeredocTokenOBraceTokenBitwiseOrTokenCBraceTokenBitwiseNotTokenOQuoteTokenCQuoteTokenTemplateControlTokenEllipsisTokenFatArrowTokenTemplateSeqEndTokenAndTokenOrTokenTemplateInterpTokenEqualOpTokenNotEqualTokenLessThanEqTokenGreaterThanEqTokenEOFTokenTabsTokenQuotedNewlineTokenStarStarTokenInvalidTokenBadUTF8"
|
||||
|
||||
var _TokenType_map = map[TokenType]string{
|
||||
0: _TokenType_name[0:8],
|
||||
10: _TokenType_name[8:20],
|
||||
33: _TokenType_name[20:29],
|
||||
37: _TokenType_name[29:41],
|
||||
38: _TokenType_name[41:56],
|
||||
39: _TokenType_name[56:71],
|
||||
40: _TokenType_name[71:82],
|
||||
41: _TokenType_name[82:93],
|
||||
42: _TokenType_name[93:102],
|
||||
43: _TokenType_name[102:111],
|
||||
44: _TokenType_name[111:121],
|
||||
45: _TokenType_name[121:131],
|
||||
46: _TokenType_name[131:139],
|
||||
47: _TokenType_name[139:149],
|
||||
58: _TokenType_name[149:159],
|
||||
59: _TokenType_name[159:173],
|
||||
60: _TokenType_name[173:186],
|
||||
61: _TokenType_name[186:196],
|
||||
62: _TokenType_name[196:212],
|
||||
63: _TokenType_name[212:225],
|
||||
67: _TokenType_name[225:237],
|
||||
72: _TokenType_name[237:250],
|
||||
73: _TokenType_name[250:260],
|
||||
78: _TokenType_name[260:274],
|
||||
81: _TokenType_name[274:288],
|
||||
83: _TokenType_name[288:302],
|
||||
91: _TokenType_name[302:313],
|
||||
93: _TokenType_name[313:324],
|
||||
94: _TokenType_name[324:339],
|
||||
96: _TokenType_name[339:352],
|
||||
104: _TokenType_name[352:365],
|
||||
123: _TokenType_name[365:376],
|
||||
124: _TokenType_name[376:390],
|
||||
125: _TokenType_name[390:401],
|
||||
126: _TokenType_name[401:416],
|
||||
171: _TokenType_name[416:427],
|
||||
187: _TokenType_name[427:438],
|
||||
955: _TokenType_name[438:458],
|
||||
8230: _TokenType_name[458:471],
|
||||
8658: _TokenType_name[471:484],
|
||||
8718: _TokenType_name[484:503],
|
||||
8743: _TokenType_name[503:511],
|
||||
8744: _TokenType_name[511:518],
|
||||
8747: _TokenType_name[518:537],
|
||||
8788: _TokenType_name[537:549],
|
||||
8800: _TokenType_name[549:562],
|
||||
8804: _TokenType_name[562:577],
|
||||
8805: _TokenType_name[577:595],
|
||||
9220: _TokenType_name[595:603],
|
||||
9225: _TokenType_name[603:612],
|
||||
9252: _TokenType_name[612:630],
|
||||
10138: _TokenType_name[630:643],
|
||||
65533: _TokenType_name[643:655],
|
||||
128169: _TokenType_name[655:667],
|
||||
}
|
||||
|
||||
func (i TokenType) String() string {
|
||||
if str, ok := _TokenType_map[i]; ok {
|
||||
return str
|
||||
}
|
||||
return "TokenType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
335
vendor/github.com/hashicorp/hcl/v2/hclsyntax/unicode2ragel.rb
generated
vendored
Normal file
335
vendor/github.com/hashicorp/hcl/v2/hclsyntax/unicode2ragel.rb
generated
vendored
Normal file
@ -0,0 +1,335 @@
|
||||
#!/usr/bin/env ruby
|
||||
#
|
||||
# This scripted has been updated to accept more command-line arguments:
|
||||
#
|
||||
# -u, --url URL to process
|
||||
# -m, --machine Machine name
|
||||
# -p, --properties Properties to add to the machine
|
||||
# -o, --output Write output to file
|
||||
#
|
||||
# Updated by: Marty Schoch <marty.schoch@gmail.com>
|
||||
#
|
||||
# This script uses the unicode spec to generate a Ragel state machine
|
||||
# that recognizes unicode alphanumeric characters. It generates 5
|
||||
# character classes: uupper, ulower, ualpha, udigit, and ualnum.
|
||||
# Currently supported encodings are UTF-8 [default] and UCS-4.
|
||||
#
|
||||
# Usage: unicode2ragel.rb [options]
|
||||
# -e, --encoding [ucs4 | utf8] Data encoding
|
||||
# -h, --help Show this message
|
||||
#
|
||||
# This script was originally written as part of the Ferret search
|
||||
# engine library.
|
||||
#
|
||||
# Author: Rakan El-Khalil <rakan@well.com>
|
||||
|
||||
require 'optparse'
|
||||
require 'open-uri'
|
||||
|
||||
ENCODINGS = [ :utf8, :ucs4 ]
|
||||
ALPHTYPES = { :utf8 => "byte", :ucs4 => "rune" }
|
||||
DEFAULT_CHART_URL = "http://www.unicode.org/Public/5.1.0/ucd/DerivedCoreProperties.txt"
|
||||
DEFAULT_MACHINE_NAME= "WChar"
|
||||
|
||||
###
|
||||
# Display vars & default option
|
||||
|
||||
TOTAL_WIDTH = 80
|
||||
RANGE_WIDTH = 23
|
||||
@encoding = :utf8
|
||||
@chart_url = DEFAULT_CHART_URL
|
||||
machine_name = DEFAULT_MACHINE_NAME
|
||||
properties = []
|
||||
@output = $stdout
|
||||
|
||||
###
|
||||
# Option parsing
|
||||
|
||||
cli_opts = OptionParser.new do |opts|
|
||||
opts.on("-e", "--encoding [ucs4 | utf8]", "Data encoding") do |o|
|
||||
@encoding = o.downcase.to_sym
|
||||
end
|
||||
opts.on("-h", "--help", "Show this message") do
|
||||
puts opts
|
||||
exit
|
||||
end
|
||||
opts.on("-u", "--url URL", "URL to process") do |o|
|
||||
@chart_url = o
|
||||
end
|
||||
opts.on("-m", "--machine MACHINE_NAME", "Machine name") do |o|
|
||||
machine_name = o
|
||||
end
|
||||
opts.on("-p", "--properties x,y,z", Array, "Properties to add to machine") do |o|
|
||||
properties = o
|
||||
end
|
||||
opts.on("-o", "--output FILE", "output file") do |o|
|
||||
@output = File.new(o, "w+")
|
||||
end
|
||||
end
|
||||
|
||||
cli_opts.parse(ARGV)
|
||||
unless ENCODINGS.member? @encoding
|
||||
puts "Invalid encoding: #{@encoding}"
|
||||
puts cli_opts
|
||||
exit
|
||||
end
|
||||
|
||||
##
|
||||
# Downloads the document at url and yields every alpha line's hex
|
||||
# range and description.
|
||||
|
||||
def each_alpha( url, property )
|
||||
open( url ) do |file|
|
||||
file.each_line do |line|
|
||||
next if line =~ /^#/;
|
||||
next if line !~ /; #{property} #/;
|
||||
|
||||
range, description = line.split(/;/)
|
||||
range.strip!
|
||||
description.gsub!(/.*#/, '').strip!
|
||||
|
||||
if range =~ /\.\./
|
||||
start, stop = range.split '..'
|
||||
else start = stop = range
|
||||
end
|
||||
|
||||
yield start.hex .. stop.hex, description
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
###
|
||||
# Formats to hex at minimum width
|
||||
|
||||
def to_hex( n )
|
||||
r = "%0X" % n
|
||||
r = "0#{r}" unless (r.length % 2).zero?
|
||||
r
|
||||
end
|
||||
|
||||
###
|
||||
# UCS4 is just a straight hex conversion of the unicode codepoint.
|
||||
|
||||
def to_ucs4( range )
|
||||
rangestr = "0x" + to_hex(range.begin)
|
||||
rangestr << "..0x" + to_hex(range.end) if range.begin != range.end
|
||||
[ rangestr ]
|
||||
end
|
||||
|
||||
##
|
||||
# 0x00 - 0x7f -> 0zzzzzzz[7]
|
||||
# 0x80 - 0x7ff -> 110yyyyy[5] 10zzzzzz[6]
|
||||
# 0x800 - 0xffff -> 1110xxxx[4] 10yyyyyy[6] 10zzzzzz[6]
|
||||
# 0x010000 - 0x10ffff -> 11110www[3] 10xxxxxx[6] 10yyyyyy[6] 10zzzzzz[6]
|
||||
|
||||
UTF8_BOUNDARIES = [0x7f, 0x7ff, 0xffff, 0x10ffff]
|
||||
|
||||
def to_utf8_enc( n )
|
||||
r = 0
|
||||
if n <= 0x7f
|
||||
r = n
|
||||
elsif n <= 0x7ff
|
||||
y = 0xc0 | (n >> 6)
|
||||
z = 0x80 | (n & 0x3f)
|
||||
r = y << 8 | z
|
||||
elsif n <= 0xffff
|
||||
x = 0xe0 | (n >> 12)
|
||||
y = 0x80 | (n >> 6) & 0x3f
|
||||
z = 0x80 | n & 0x3f
|
||||
r = x << 16 | y << 8 | z
|
||||
elsif n <= 0x10ffff
|
||||
w = 0xf0 | (n >> 18)
|
||||
x = 0x80 | (n >> 12) & 0x3f
|
||||
y = 0x80 | (n >> 6) & 0x3f
|
||||
z = 0x80 | n & 0x3f
|
||||
r = w << 24 | x << 16 | y << 8 | z
|
||||
end
|
||||
|
||||
to_hex(r)
|
||||
end
|
||||
|
||||
def from_utf8_enc( n )
|
||||
n = n.hex
|
||||
r = 0
|
||||
if n <= 0x7f
|
||||
r = n
|
||||
elsif n <= 0xdfff
|
||||
y = (n >> 8) & 0x1f
|
||||
z = n & 0x3f
|
||||
r = y << 6 | z
|
||||
elsif n <= 0xefffff
|
||||
x = (n >> 16) & 0x0f
|
||||
y = (n >> 8) & 0x3f
|
||||
z = n & 0x3f
|
||||
r = x << 10 | y << 6 | z
|
||||
elsif n <= 0xf7ffffff
|
||||
w = (n >> 24) & 0x07
|
||||
x = (n >> 16) & 0x3f
|
||||
y = (n >> 8) & 0x3f
|
||||
z = n & 0x3f
|
||||
r = w << 18 | x << 12 | y << 6 | z
|
||||
end
|
||||
r
|
||||
end
|
||||
|
||||
###
|
||||
# Given a range, splits it up into ranges that can be continuously
|
||||
# encoded into utf8. Eg: 0x00 .. 0xff => [0x00..0x7f, 0x80..0xff]
|
||||
# This is not strictly needed since the current [5.1] unicode standard
|
||||
# doesn't have ranges that straddle utf8 boundaries. This is included
|
||||
# for completeness as there is no telling if that will ever change.
|
||||
|
||||
def utf8_ranges( range )
|
||||
ranges = []
|
||||
UTF8_BOUNDARIES.each do |max|
|
||||
if range.begin <= max
|
||||
if range.end <= max
|
||||
ranges << range
|
||||
return ranges
|
||||
end
|
||||
|
||||
ranges << (range.begin .. max)
|
||||
range = (max + 1) .. range.end
|
||||
end
|
||||
end
|
||||
ranges
|
||||
end
|
||||
|
||||
def build_range( start, stop )
|
||||
size = start.size/2
|
||||
left = size - 1
|
||||
return [""] if size < 1
|
||||
|
||||
a = start[0..1]
|
||||
b = stop[0..1]
|
||||
|
||||
###
|
||||
# Shared prefix
|
||||
|
||||
if a == b
|
||||
return build_range(start[2..-1], stop[2..-1]).map do |elt|
|
||||
"0x#{a} " + elt
|
||||
end
|
||||
end
|
||||
|
||||
###
|
||||
# Unshared prefix, end of run
|
||||
|
||||
return ["0x#{a}..0x#{b} "] if left.zero?
|
||||
|
||||
###
|
||||
# Unshared prefix, not end of run
|
||||
# Range can be 0x123456..0x56789A
|
||||
# Which is equivalent to:
|
||||
# 0x123456 .. 0x12FFFF
|
||||
# 0x130000 .. 0x55FFFF
|
||||
# 0x560000 .. 0x56789A
|
||||
|
||||
ret = []
|
||||
ret << build_range(start, a + "FF" * left)
|
||||
|
||||
###
|
||||
# Only generate middle range if need be.
|
||||
|
||||
if a.hex+1 != b.hex
|
||||
max = to_hex(b.hex - 1)
|
||||
max = "FF" if b == "FF"
|
||||
ret << "0x#{to_hex(a.hex+1)}..0x#{max} " + "0x00..0xFF " * left
|
||||
end
|
||||
|
||||
###
|
||||
# Don't generate last range if it is covered by first range
|
||||
|
||||
ret << build_range(b + "00" * left, stop) unless b == "FF"
|
||||
ret.flatten!
|
||||
end
|
||||
|
||||
def to_utf8( range )
|
||||
utf8_ranges( range ).map do |r|
|
||||
begin_enc = to_utf8_enc(r.begin)
|
||||
end_enc = to_utf8_enc(r.end)
|
||||
build_range begin_enc, end_enc
|
||||
end.flatten!
|
||||
end
|
||||
|
||||
##
|
||||
# Perform a 3-way comparison of the number of codepoints advertised by
|
||||
# the unicode spec for the given range, the originally parsed range,
|
||||
# and the resulting utf8 encoded range.
|
||||
|
||||
def count_codepoints( code )
|
||||
code.split(' ').inject(1) do |acc, elt|
|
||||
if elt =~ /0x(.+)\.\.0x(.+)/
|
||||
if @encoding == :utf8
|
||||
acc * (from_utf8_enc($2) - from_utf8_enc($1) + 1)
|
||||
else
|
||||
acc * ($2.hex - $1.hex + 1)
|
||||
end
|
||||
else
|
||||
acc
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def is_valid?( range, desc, codes )
|
||||
spec_count = 1
|
||||
spec_count = $1.to_i if desc =~ /\[(\d+)\]/
|
||||
range_count = range.end - range.begin + 1
|
||||
|
||||
sum = codes.inject(0) { |acc, elt| acc + count_codepoints(elt) }
|
||||
sum == spec_count and sum == range_count
|
||||
end
|
||||
|
||||
##
|
||||
# Generate the state maching to stdout
|
||||
|
||||
def generate_machine( name, property )
|
||||
pipe = " "
|
||||
@output.puts " #{name} = "
|
||||
each_alpha( @chart_url, property ) do |range, desc|
|
||||
|
||||
codes = (@encoding == :ucs4) ? to_ucs4(range) : to_utf8(range)
|
||||
|
||||
#raise "Invalid encoding of range #{range}: #{codes.inspect}" unless
|
||||
# is_valid? range, desc, codes
|
||||
|
||||
range_width = codes.map { |a| a.size }.max
|
||||
range_width = RANGE_WIDTH if range_width < RANGE_WIDTH
|
||||
|
||||
desc_width = TOTAL_WIDTH - RANGE_WIDTH - 11
|
||||
desc_width -= (range_width - RANGE_WIDTH) if range_width > RANGE_WIDTH
|
||||
|
||||
if desc.size > desc_width
|
||||
desc = desc[0..desc_width - 4] + "..."
|
||||
end
|
||||
|
||||
codes.each_with_index do |r, idx|
|
||||
desc = "" unless idx.zero?
|
||||
code = "%-#{range_width}s" % r
|
||||
@output.puts " #{pipe} #{code} ##{desc}"
|
||||
pipe = "|"
|
||||
end
|
||||
end
|
||||
@output.puts " ;"
|
||||
@output.puts ""
|
||||
end
|
||||
|
||||
@output.puts <<EOF
|
||||
# The following Ragel file was autogenerated with #{$0}
|
||||
# from: #{@chart_url}
|
||||
#
|
||||
# It defines #{properties}.
|
||||
#
|
||||
# To use this, make sure that your alphtype is set to #{ALPHTYPES[@encoding]},
|
||||
# and that your input is in #{@encoding}.
|
||||
|
||||
%%{
|
||||
machine #{machine_name};
|
||||
|
||||
EOF
|
||||
|
||||
properties.each { |x| generate_machine( x, x ) }
|
||||
|
||||
@output.puts <<EOF
|
||||
}%%
|
||||
EOF
|
2135
vendor/github.com/hashicorp/hcl/v2/hclsyntax/unicode_derived.rl
generated
vendored
Normal file
2135
vendor/github.com/hashicorp/hcl/v2/hclsyntax/unicode_derived.rl
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
86
vendor/github.com/hashicorp/hcl/v2/hclsyntax/variables.go
generated
vendored
Normal file
86
vendor/github.com/hashicorp/hcl/v2/hclsyntax/variables.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// Variables returns all of the variables referenced within a given experssion.
|
||||
//
|
||||
// This is the implementation of the "Variables" method on every native
|
||||
// expression.
|
||||
func Variables(expr Expression) []hcl.Traversal {
|
||||
var vars []hcl.Traversal
|
||||
|
||||
walker := &variablesWalker{
|
||||
Callback: func(t hcl.Traversal) {
|
||||
vars = append(vars, t)
|
||||
},
|
||||
}
|
||||
|
||||
Walk(expr, walker)
|
||||
|
||||
return vars
|
||||
}
|
||||
|
||||
// variablesWalker is a Walker implementation that calls its callback for any
|
||||
// root scope traversal found while walking.
|
||||
type variablesWalker struct {
|
||||
Callback func(hcl.Traversal)
|
||||
localScopes []map[string]struct{}
|
||||
}
|
||||
|
||||
func (w *variablesWalker) Enter(n Node) hcl.Diagnostics {
|
||||
switch tn := n.(type) {
|
||||
case *ScopeTraversalExpr:
|
||||
t := tn.Traversal
|
||||
|
||||
// Check if the given root name appears in any of the active
|
||||
// local scopes. We don't want to return local variables here, since
|
||||
// the goal of walking variables is to tell the calling application
|
||||
// which names it needs to populate in the _root_ scope.
|
||||
name := t.RootName()
|
||||
for _, names := range w.localScopes {
|
||||
if _, localized := names[name]; localized {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
w.Callback(t)
|
||||
case ChildScope:
|
||||
w.localScopes = append(w.localScopes, tn.LocalNames)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *variablesWalker) Exit(n Node) hcl.Diagnostics {
|
||||
switch n.(type) {
|
||||
case ChildScope:
|
||||
// pop the latest local scope, assuming that the walker will
|
||||
// behave symmetrically as promised.
|
||||
w.localScopes = w.localScopes[:len(w.localScopes)-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChildScope is a synthetic AST node that is visited during a walk to
|
||||
// indicate that its descendent will be evaluated in a child scope, which
|
||||
// may mask certain variables from the parent scope as locals.
|
||||
//
|
||||
// ChildScope nodes don't really exist in the AST, but are rather synthesized
|
||||
// on the fly during walk. Therefore it doesn't do any good to transform them;
|
||||
// instead, transform either parent node that created a scope or the expression
|
||||
// that the child scope struct wraps.
|
||||
type ChildScope struct {
|
||||
LocalNames map[string]struct{}
|
||||
Expr Expression
|
||||
}
|
||||
|
||||
func (e ChildScope) walkChildNodes(w internalWalkFunc) {
|
||||
w(e.Expr)
|
||||
}
|
||||
|
||||
// Range returns the range of the expression that the ChildScope is
|
||||
// encapsulating. It isn't really very useful to call Range on a ChildScope.
|
||||
func (e ChildScope) Range() hcl.Range {
|
||||
return e.Expr.Range()
|
||||
}
|
41
vendor/github.com/hashicorp/hcl/v2/hclsyntax/walk.go
generated
vendored
Normal file
41
vendor/github.com/hashicorp/hcl/v2/hclsyntax/walk.go
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// VisitFunc is the callback signature for VisitAll.
|
||||
type VisitFunc func(node Node) hcl.Diagnostics
|
||||
|
||||
// VisitAll is a basic way to traverse the AST beginning with a particular
|
||||
// node. The given function will be called once for each AST node in
|
||||
// depth-first order, but no context is provided about the shape of the tree.
|
||||
//
|
||||
// The VisitFunc may return diagnostics, in which case they will be accumulated
|
||||
// and returned as a single set.
|
||||
func VisitAll(node Node, f VisitFunc) hcl.Diagnostics {
|
||||
diags := f(node)
|
||||
node.walkChildNodes(func(node Node) {
|
||||
diags = append(diags, VisitAll(node, f)...)
|
||||
})
|
||||
return diags
|
||||
}
|
||||
|
||||
// Walker is an interface used with Walk.
|
||||
type Walker interface {
|
||||
Enter(node Node) hcl.Diagnostics
|
||||
Exit(node Node) hcl.Diagnostics
|
||||
}
|
||||
|
||||
// Walk is a more complex way to traverse the AST starting with a particular
|
||||
// node, which provides information about the tree structure via separate
|
||||
// Enter and Exit functions.
|
||||
func Walk(node Node, w Walker) hcl.Diagnostics {
|
||||
diags := w.Enter(node)
|
||||
node.walkChildNodes(func(node Node) {
|
||||
diags = append(diags, Walk(node, w)...)
|
||||
})
|
||||
moreDiags := w.Exit(node)
|
||||
diags = append(diags, moreDiags...)
|
||||
return diags
|
||||
}
|
121
vendor/github.com/hashicorp/hcl/v2/hclwrite/ast.go
generated
vendored
Normal file
121
vendor/github.com/hashicorp/hcl/v2/hclwrite/ast.go
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
package hclwrite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
inTree
|
||||
|
||||
srcBytes []byte
|
||||
body *node
|
||||
}
|
||||
|
||||
// NewEmptyFile constructs a new file with no content, ready to be mutated
|
||||
// by other calls that append to its body.
|
||||
func NewEmptyFile() *File {
|
||||
f := &File{
|
||||
inTree: newInTree(),
|
||||
}
|
||||
body := newBody()
|
||||
f.body = f.children.Append(body)
|
||||
return f
|
||||
}
|
||||
|
||||
// Body returns the root body of the file, which contains the top-level
|
||||
// attributes and blocks.
|
||||
func (f *File) Body() *Body {
|
||||
return f.body.content.(*Body)
|
||||
}
|
||||
|
||||
// WriteTo writes the tokens underlying the receiving file to the given writer.
|
||||
//
|
||||
// The tokens first have a simple formatting pass applied that adjusts only
|
||||
// the spaces between them.
|
||||
func (f *File) WriteTo(wr io.Writer) (int64, error) {
|
||||
tokens := f.inTree.children.BuildTokens(nil)
|
||||
format(tokens)
|
||||
return tokens.WriteTo(wr)
|
||||
}
|
||||
|
||||
// Bytes returns a buffer containing the source code resulting from the
|
||||
// tokens underlying the receiving file. If any updates have been made via
|
||||
// the AST API, these will be reflected in the result.
|
||||
func (f *File) Bytes() []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
f.WriteTo(buf)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
type comments struct {
|
||||
leafNode
|
||||
|
||||
parent *node
|
||||
tokens Tokens
|
||||
}
|
||||
|
||||
func newComments(tokens Tokens) *comments {
|
||||
return &comments{
|
||||
tokens: tokens,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *comments) BuildTokens(to Tokens) Tokens {
|
||||
return c.tokens.BuildTokens(to)
|
||||
}
|
||||
|
||||
type identifier struct {
|
||||
leafNode
|
||||
|
||||
parent *node
|
||||
token *Token
|
||||
}
|
||||
|
||||
func newIdentifier(token *Token) *identifier {
|
||||
return &identifier{
|
||||
token: token,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *identifier) BuildTokens(to Tokens) Tokens {
|
||||
return append(to, i.token)
|
||||
}
|
||||
|
||||
func (i *identifier) hasName(name string) bool {
|
||||
return name == string(i.token.Bytes)
|
||||
}
|
||||
|
||||
type number struct {
|
||||
leafNode
|
||||
|
||||
parent *node
|
||||
token *Token
|
||||
}
|
||||
|
||||
func newNumber(token *Token) *number {
|
||||
return &number{
|
||||
token: token,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *number) BuildTokens(to Tokens) Tokens {
|
||||
return append(to, n.token)
|
||||
}
|
||||
|
||||
type quoted struct {
|
||||
leafNode
|
||||
|
||||
parent *node
|
||||
tokens Tokens
|
||||
}
|
||||
|
||||
func newQuoted(tokens Tokens) *quoted {
|
||||
return "ed{
|
||||
tokens: tokens,
|
||||
}
|
||||
}
|
||||
|
||||
func (q *quoted) BuildTokens(to Tokens) Tokens {
|
||||
return q.tokens.BuildTokens(to)
|
||||
}
|
48
vendor/github.com/hashicorp/hcl/v2/hclwrite/ast_attribute.go
generated
vendored
Normal file
48
vendor/github.com/hashicorp/hcl/v2/hclwrite/ast_attribute.go
generated
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
package hclwrite
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
)
|
||||
|
||||
type Attribute struct {
|
||||
inTree
|
||||
|
||||
leadComments *node
|
||||
name *node
|
||||
expr *node
|
||||
lineComments *node
|
||||
}
|
||||
|
||||
func newAttribute() *Attribute {
|
||||
return &Attribute{
|
||||
inTree: newInTree(),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Attribute) init(name string, expr *Expression) {
|
||||
expr.assertUnattached()
|
||||
|
||||
nameTok := newIdentToken(name)
|
||||
nameObj := newIdentifier(nameTok)
|
||||
a.leadComments = a.children.Append(newComments(nil))
|
||||
a.name = a.children.Append(nameObj)
|
||||
a.children.AppendUnstructuredTokens(Tokens{
|
||||
{
|
||||
Type: hclsyntax.TokenEqual,
|
||||
Bytes: []byte{'='},
|
||||
},
|
||||
})
|
||||
a.expr = a.children.Append(expr)
|
||||
a.expr.list = a.children
|
||||
a.lineComments = a.children.Append(newComments(nil))
|
||||
a.children.AppendUnstructuredTokens(Tokens{
|
||||
{
|
||||
Type: hclsyntax.TokenNewline,
|
||||
Bytes: []byte{'\n'},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (a *Attribute) Expr() *Expression {
|
||||
return a.expr.content.(*Expression)
|
||||
}
|
118
vendor/github.com/hashicorp/hcl/v2/hclwrite/ast_block.go
generated
vendored
Normal file
118
vendor/github.com/hashicorp/hcl/v2/hclwrite/ast_block.go
generated
vendored
Normal file
@ -0,0 +1,118 @@
|
||||
package hclwrite
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
type Block struct {
|
||||
inTree
|
||||
|
||||
leadComments *node
|
||||
typeName *node
|
||||
labels nodeSet
|
||||
open *node
|
||||
body *node
|
||||
close *node
|
||||
}
|
||||
|
||||
func newBlock() *Block {
|
||||
return &Block{
|
||||
inTree: newInTree(),
|
||||
labels: newNodeSet(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewBlock constructs a new, empty block with the given type name and labels.
|
||||
func NewBlock(typeName string, labels []string) *Block {
|
||||
block := newBlock()
|
||||
block.init(typeName, labels)
|
||||
return block
|
||||
}
|
||||
|
||||
func (b *Block) init(typeName string, labels []string) {
|
||||
nameTok := newIdentToken(typeName)
|
||||
nameObj := newIdentifier(nameTok)
|
||||
b.leadComments = b.children.Append(newComments(nil))
|
||||
b.typeName = b.children.Append(nameObj)
|
||||
for _, label := range labels {
|
||||
labelToks := TokensForValue(cty.StringVal(label))
|
||||
labelObj := newQuoted(labelToks)
|
||||
labelNode := b.children.Append(labelObj)
|
||||
b.labels.Add(labelNode)
|
||||
}
|
||||
b.open = b.children.AppendUnstructuredTokens(Tokens{
|
||||
{
|
||||
Type: hclsyntax.TokenOBrace,
|
||||
Bytes: []byte{'{'},
|
||||
},
|
||||
{
|
||||
Type: hclsyntax.TokenNewline,
|
||||
Bytes: []byte{'\n'},
|
||||
},
|
||||
})
|
||||
body := newBody() // initially totally empty; caller can append to it subsequently
|
||||
b.body = b.children.Append(body)
|
||||
b.close = b.children.AppendUnstructuredTokens(Tokens{
|
||||
{
|
||||
Type: hclsyntax.TokenCBrace,
|
||||
Bytes: []byte{'}'},
|
||||
},
|
||||
{
|
||||
Type: hclsyntax.TokenNewline,
|
||||
Bytes: []byte{'\n'},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Body returns the body that represents the content of the receiving block.
|
||||
//
|
||||
// Appending to or otherwise modifying this body will make changes to the
|
||||
// tokens that are generated between the blocks open and close braces.
|
||||
func (b *Block) Body() *Body {
|
||||
return b.body.content.(*Body)
|
||||
}
|
||||
|
||||
// Type returns the type name of the block.
|
||||
func (b *Block) Type() string {
|
||||
typeNameObj := b.typeName.content.(*identifier)
|
||||
return string(typeNameObj.token.Bytes)
|
||||
}
|
||||
|
||||
// Labels returns the labels of the block.
|
||||
func (b *Block) Labels() []string {
|
||||
labelNames := make([]string, 0, len(b.labels))
|
||||
list := b.labels.List()
|
||||
|
||||
for _, label := range list {
|
||||
switch labelObj := label.content.(type) {
|
||||
case *identifier:
|
||||
if labelObj.token.Type == hclsyntax.TokenIdent {
|
||||
labelString := string(labelObj.token.Bytes)
|
||||
labelNames = append(labelNames, labelString)
|
||||
}
|
||||
|
||||
case *quoted:
|
||||
tokens := labelObj.tokens
|
||||
if len(tokens) == 3 &&
|
||||
tokens[0].Type == hclsyntax.TokenOQuote &&
|
||||
tokens[1].Type == hclsyntax.TokenQuotedLit &&
|
||||
tokens[2].Type == hclsyntax.TokenCQuote {
|
||||
// Note that TokenQuotedLit may contain escape sequences.
|
||||
labelString, diags := hclsyntax.ParseStringLiteralToken(tokens[1].asHCLSyntax())
|
||||
|
||||
// If parsing the string literal returns error diagnostics
|
||||
// then we can just assume the label doesn't match, because it's invalid in some way.
|
||||
if !diags.HasErrors() {
|
||||
labelNames = append(labelNames, labelString)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
// If neither of the previous cases are true (should be impossible)
|
||||
// then we can just ignore it, because it's invalid too.
|
||||
}
|
||||
}
|
||||
|
||||
return labelNames
|
||||
}
|
239
vendor/github.com/hashicorp/hcl/v2/hclwrite/ast_body.go
generated
vendored
Normal file
239
vendor/github.com/hashicorp/hcl/v2/hclwrite/ast_body.go
generated
vendored
Normal file
@ -0,0 +1,239 @@
|
||||
package hclwrite
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
type Body struct {
|
||||
inTree
|
||||
|
||||
items nodeSet
|
||||
}
|
||||
|
||||
func newBody() *Body {
|
||||
return &Body{
|
||||
inTree: newInTree(),
|
||||
items: newNodeSet(),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Body) appendItem(c nodeContent) *node {
|
||||
nn := b.children.Append(c)
|
||||
b.items.Add(nn)
|
||||
return nn
|
||||
}
|
||||
|
||||
func (b *Body) appendItemNode(nn *node) *node {
|
||||
nn.assertUnattached()
|
||||
b.children.AppendNode(nn)
|
||||
b.items.Add(nn)
|
||||
return nn
|
||||
}
|
||||
|
||||
// Clear removes all of the items from the body, making it empty.
|
||||
func (b *Body) Clear() {
|
||||
b.children.Clear()
|
||||
}
|
||||
|
||||
func (b *Body) AppendUnstructuredTokens(ts Tokens) {
|
||||
b.inTree.children.Append(ts)
|
||||
}
|
||||
|
||||
// Attributes returns a new map of all of the attributes in the body, with
|
||||
// the attribute names as the keys.
|
||||
func (b *Body) Attributes() map[string]*Attribute {
|
||||
ret := make(map[string]*Attribute)
|
||||
for n := range b.items {
|
||||
if attr, isAttr := n.content.(*Attribute); isAttr {
|
||||
nameObj := attr.name.content.(*identifier)
|
||||
name := string(nameObj.token.Bytes)
|
||||
ret[name] = attr
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Blocks returns a new slice of all the blocks in the body.
|
||||
func (b *Body) Blocks() []*Block {
|
||||
ret := make([]*Block, 0, len(b.items))
|
||||
for _, n := range b.items.List() {
|
||||
if block, isBlock := n.content.(*Block); isBlock {
|
||||
ret = append(ret, block)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// GetAttribute returns the attribute from the body that has the given name,
|
||||
// or returns nil if there is currently no matching attribute.
|
||||
func (b *Body) GetAttribute(name string) *Attribute {
|
||||
for n := range b.items {
|
||||
if attr, isAttr := n.content.(*Attribute); isAttr {
|
||||
nameObj := attr.name.content.(*identifier)
|
||||
if nameObj.hasName(name) {
|
||||
// We've found it!
|
||||
return attr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getAttributeNode is like GetAttribute but it returns the node containing
|
||||
// the selected attribute (if one is found) rather than the attribute itself.
|
||||
func (b *Body) getAttributeNode(name string) *node {
|
||||
for n := range b.items {
|
||||
if attr, isAttr := n.content.(*Attribute); isAttr {
|
||||
nameObj := attr.name.content.(*identifier)
|
||||
if nameObj.hasName(name) {
|
||||
// We've found it!
|
||||
return n
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FirstMatchingBlock returns a first matching block from the body that has the
|
||||
// given name and labels or returns nil if there is currently no matching
|
||||
// block.
|
||||
func (b *Body) FirstMatchingBlock(typeName string, labels []string) *Block {
|
||||
for _, block := range b.Blocks() {
|
||||
if typeName == block.Type() {
|
||||
labelNames := block.Labels()
|
||||
if len(labels) == 0 && len(labelNames) == 0 {
|
||||
return block
|
||||
}
|
||||
if reflect.DeepEqual(labels, labelNames) {
|
||||
return block
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveBlock removes the given block from the body, if it's in that body.
|
||||
// If it isn't present, this is a no-op.
|
||||
//
|
||||
// Returns true if it removed something, or false otherwise.
|
||||
func (b *Body) RemoveBlock(block *Block) bool {
|
||||
for n := range b.items {
|
||||
if n.content == block {
|
||||
n.Detach()
|
||||
b.items.Remove(n)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAttributeRaw either replaces the expression of an existing attribute
|
||||
// of the given name or adds a new attribute definition to the end of the block,
|
||||
// using the given tokens verbatim as the expression.
|
||||
//
|
||||
// The same caveats apply to this function as for NewExpressionRaw on which
|
||||
// it is based. If possible, prefer to use SetAttributeValue or
|
||||
// SetAttributeTraversal.
|
||||
func (b *Body) SetAttributeRaw(name string, tokens Tokens) *Attribute {
|
||||
attr := b.GetAttribute(name)
|
||||
expr := NewExpressionRaw(tokens)
|
||||
if attr != nil {
|
||||
attr.expr = attr.expr.ReplaceWith(expr)
|
||||
} else {
|
||||
attr := newAttribute()
|
||||
attr.init(name, expr)
|
||||
b.appendItem(attr)
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
// SetAttributeValue either replaces the expression of an existing attribute
|
||||
// of the given name or adds a new attribute definition to the end of the block.
|
||||
//
|
||||
// The value is given as a cty.Value, and must therefore be a literal. To set
|
||||
// a variable reference or other traversal, use SetAttributeTraversal.
|
||||
//
|
||||
// The return value is the attribute that was either modified in-place or
|
||||
// created.
|
||||
func (b *Body) SetAttributeValue(name string, val cty.Value) *Attribute {
|
||||
attr := b.GetAttribute(name)
|
||||
expr := NewExpressionLiteral(val)
|
||||
if attr != nil {
|
||||
attr.expr = attr.expr.ReplaceWith(expr)
|
||||
} else {
|
||||
attr := newAttribute()
|
||||
attr.init(name, expr)
|
||||
b.appendItem(attr)
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
// SetAttributeTraversal either replaces the expression of an existing attribute
|
||||
// of the given name or adds a new attribute definition to the end of the body.
|
||||
//
|
||||
// The new expression is given as a hcl.Traversal, which must be an absolute
|
||||
// traversal. To set a literal value, use SetAttributeValue.
|
||||
//
|
||||
// The return value is the attribute that was either modified in-place or
|
||||
// created.
|
||||
func (b *Body) SetAttributeTraversal(name string, traversal hcl.Traversal) *Attribute {
|
||||
attr := b.GetAttribute(name)
|
||||
expr := NewExpressionAbsTraversal(traversal)
|
||||
if attr != nil {
|
||||
attr.expr = attr.expr.ReplaceWith(expr)
|
||||
} else {
|
||||
attr := newAttribute()
|
||||
attr.init(name, expr)
|
||||
b.appendItem(attr)
|
||||
}
|
||||
return attr
|
||||
}
|
||||
|
||||
// RemoveAttribute removes the attribute with the given name from the body.
|
||||
//
|
||||
// The return value is the attribute that was removed, or nil if there was
|
||||
// no such attribute (in which case the call was a no-op).
|
||||
func (b *Body) RemoveAttribute(name string) *Attribute {
|
||||
node := b.getAttributeNode(name)
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
node.Detach()
|
||||
b.items.Remove(node)
|
||||
return node.content.(*Attribute)
|
||||
}
|
||||
|
||||
// AppendBlock appends an existing block (which must not be already attached
|
||||
// to a body) to the end of the receiving body.
|
||||
func (b *Body) AppendBlock(block *Block) *Block {
|
||||
b.appendItem(block)
|
||||
return block
|
||||
}
|
||||
|
||||
// AppendNewBlock appends a new nested block to the end of the receiving body
|
||||
// with the given type name and labels.
|
||||
func (b *Body) AppendNewBlock(typeName string, labels []string) *Block {
|
||||
block := newBlock()
|
||||
block.init(typeName, labels)
|
||||
b.appendItem(block)
|
||||
return block
|
||||
}
|
||||
|
||||
// AppendNewline appends a newline token to th end of the receiving body,
|
||||
// which generally serves as a separator between different sets of body
|
||||
// contents.
|
||||
func (b *Body) AppendNewline() {
|
||||
b.AppendUnstructuredTokens(Tokens{
|
||||
{
|
||||
Type: hclsyntax.TokenNewline,
|
||||
Bytes: []byte{'\n'},
|
||||
},
|
||||
})
|
||||
}
|
224
vendor/github.com/hashicorp/hcl/v2/hclwrite/ast_expression.go
generated
vendored
Normal file
224
vendor/github.com/hashicorp/hcl/v2/hclwrite/ast_expression.go
generated
vendored
Normal file
@ -0,0 +1,224 @@
|
||||
package hclwrite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
type Expression struct {
|
||||
inTree
|
||||
|
||||
absTraversals nodeSet
|
||||
}
|
||||
|
||||
func newExpression() *Expression {
|
||||
return &Expression{
|
||||
inTree: newInTree(),
|
||||
absTraversals: newNodeSet(),
|
||||
}
|
||||
}
|
||||
|
||||
// NewExpressionRaw constructs an expression containing the given raw tokens.
|
||||
//
|
||||
// There is no automatic validation that the given tokens produce a valid
|
||||
// expression. Callers of thus function must take care to produce invalid
|
||||
// expression tokens. Where possible, use the higher-level functions
|
||||
// NewExpressionLiteral or NewExpressionAbsTraversal instead.
|
||||
//
|
||||
// Because NewExpressionRaw does not interpret the given tokens in any way,
|
||||
// an expression created by NewExpressionRaw will produce an empty result
|
||||
// for calls to its method Variables, even if the given token sequence
|
||||
// contains a subslice that would normally be interpreted as a traversal under
|
||||
// parsing.
|
||||
func NewExpressionRaw(tokens Tokens) *Expression {
|
||||
expr := newExpression()
|
||||
// We copy the tokens here in order to make sure that later mutations
|
||||
// by the caller don't inadvertently cause our expression to become
|
||||
// invalid.
|
||||
copyTokens := make(Tokens, len(tokens))
|
||||
copy(copyTokens, tokens)
|
||||
expr.children.AppendUnstructuredTokens(copyTokens)
|
||||
return expr
|
||||
}
|
||||
|
||||
// NewExpressionLiteral constructs an an expression that represents the given
|
||||
// literal value.
|
||||
//
|
||||
// Since an unknown value cannot be represented in source code, this function
|
||||
// will panic if the given value is unknown or contains a nested unknown value.
|
||||
// Use val.IsWhollyKnown before calling to be sure.
|
||||
//
|
||||
// HCL native syntax does not directly represent lists, maps, and sets, and
|
||||
// instead relies on the automatic conversions to those collection types from
|
||||
// either list or tuple constructor syntax. Therefore converting collection
|
||||
// values to source code and re-reading them will lose type information, and
|
||||
// the reader must provide a suitable type at decode time to recover the
|
||||
// original value.
|
||||
func NewExpressionLiteral(val cty.Value) *Expression {
|
||||
toks := TokensForValue(val)
|
||||
expr := newExpression()
|
||||
expr.children.AppendUnstructuredTokens(toks)
|
||||
return expr
|
||||
}
|
||||
|
||||
// NewExpressionAbsTraversal constructs an expression that represents the
|
||||
// given traversal, which must be absolute or this function will panic.
|
||||
func NewExpressionAbsTraversal(traversal hcl.Traversal) *Expression {
|
||||
if traversal.IsRelative() {
|
||||
panic("can't construct expression from relative traversal")
|
||||
}
|
||||
|
||||
physT := newTraversal()
|
||||
rootName := traversal.RootName()
|
||||
steps := traversal[1:]
|
||||
|
||||
{
|
||||
tn := newTraverseName()
|
||||
tn.name = tn.children.Append(newIdentifier(&Token{
|
||||
Type: hclsyntax.TokenIdent,
|
||||
Bytes: []byte(rootName),
|
||||
}))
|
||||
physT.steps.Add(physT.children.Append(tn))
|
||||
}
|
||||
|
||||
for _, step := range steps {
|
||||
switch ts := step.(type) {
|
||||
case hcl.TraverseAttr:
|
||||
tn := newTraverseName()
|
||||
tn.children.AppendUnstructuredTokens(Tokens{
|
||||
{
|
||||
Type: hclsyntax.TokenDot,
|
||||
Bytes: []byte{'.'},
|
||||
},
|
||||
})
|
||||
tn.name = tn.children.Append(newIdentifier(&Token{
|
||||
Type: hclsyntax.TokenIdent,
|
||||
Bytes: []byte(ts.Name),
|
||||
}))
|
||||
physT.steps.Add(physT.children.Append(tn))
|
||||
case hcl.TraverseIndex:
|
||||
ti := newTraverseIndex()
|
||||
ti.children.AppendUnstructuredTokens(Tokens{
|
||||
{
|
||||
Type: hclsyntax.TokenOBrack,
|
||||
Bytes: []byte{'['},
|
||||
},
|
||||
})
|
||||
indexExpr := NewExpressionLiteral(ts.Key)
|
||||
ti.key = ti.children.Append(indexExpr)
|
||||
ti.children.AppendUnstructuredTokens(Tokens{
|
||||
{
|
||||
Type: hclsyntax.TokenCBrack,
|
||||
Bytes: []byte{']'},
|
||||
},
|
||||
})
|
||||
physT.steps.Add(physT.children.Append(ti))
|
||||
}
|
||||
}
|
||||
|
||||
expr := newExpression()
|
||||
expr.absTraversals.Add(expr.children.Append(physT))
|
||||
return expr
|
||||
}
|
||||
|
||||
// Variables returns the absolute traversals that exist within the receiving
|
||||
// expression.
|
||||
func (e *Expression) Variables() []*Traversal {
|
||||
nodes := e.absTraversals.List()
|
||||
ret := make([]*Traversal, len(nodes))
|
||||
for i, node := range nodes {
|
||||
ret[i] = node.content.(*Traversal)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// RenameVariablePrefix examines each of the absolute traversals in the
|
||||
// receiving expression to see if they have the given sequence of names as
|
||||
// a prefix prefix. If so, they are updated in place to have the given
|
||||
// replacement names instead of that prefix.
|
||||
//
|
||||
// This can be used to implement symbol renaming. The calling application can
|
||||
// visit all relevant expressions in its input and apply the same renaming
|
||||
// to implement a global symbol rename.
|
||||
//
|
||||
// The search and replacement traversals must be the same length, or this
|
||||
// method will panic. Only attribute access operations can be matched and
|
||||
// replaced. Index steps never match the prefix.
|
||||
func (e *Expression) RenameVariablePrefix(search, replacement []string) {
|
||||
if len(search) != len(replacement) {
|
||||
panic(fmt.Sprintf("search and replacement length mismatch (%d and %d)", len(search), len(replacement)))
|
||||
}
|
||||
Traversals:
|
||||
for node := range e.absTraversals {
|
||||
traversal := node.content.(*Traversal)
|
||||
if len(traversal.steps) < len(search) {
|
||||
// If it's shorter then it can't have our prefix
|
||||
continue
|
||||
}
|
||||
|
||||
stepNodes := traversal.steps.List()
|
||||
for i, name := range search {
|
||||
step, isName := stepNodes[i].content.(*TraverseName)
|
||||
if !isName {
|
||||
continue Traversals // only name nodes can match
|
||||
}
|
||||
foundNameBytes := step.name.content.(*identifier).token.Bytes
|
||||
if len(foundNameBytes) != len(name) {
|
||||
continue Traversals
|
||||
}
|
||||
if string(foundNameBytes) != name {
|
||||
continue Traversals
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here then the prefix matched, so now we'll swap in
|
||||
// the replacement strings.
|
||||
for i, name := range replacement {
|
||||
step := stepNodes[i].content.(*TraverseName)
|
||||
token := step.name.content.(*identifier).token
|
||||
token.Bytes = []byte(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Traversal represents a sequence of variable, attribute, and/or index
|
||||
// operations.
|
||||
type Traversal struct {
|
||||
inTree
|
||||
|
||||
steps nodeSet
|
||||
}
|
||||
|
||||
func newTraversal() *Traversal {
|
||||
return &Traversal{
|
||||
inTree: newInTree(),
|
||||
steps: newNodeSet(),
|
||||
}
|
||||
}
|
||||
|
||||
type TraverseName struct {
|
||||
inTree
|
||||
|
||||
name *node
|
||||
}
|
||||
|
||||
func newTraverseName() *TraverseName {
|
||||
return &TraverseName{
|
||||
inTree: newInTree(),
|
||||
}
|
||||
}
|
||||
|
||||
type TraverseIndex struct {
|
||||
inTree
|
||||
|
||||
key *node
|
||||
}
|
||||
|
||||
func newTraverseIndex() *TraverseIndex {
|
||||
return &TraverseIndex{
|
||||
inTree: newInTree(),
|
||||
}
|
||||
}
|
11
vendor/github.com/hashicorp/hcl/v2/hclwrite/doc.go
generated
vendored
Normal file
11
vendor/github.com/hashicorp/hcl/v2/hclwrite/doc.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
// Package hclwrite deals with the problem of generating HCL configuration
|
||||
// and of making specific surgical changes to existing HCL configurations.
|
||||
//
|
||||
// It operates at a different level of abstraction than the main HCL parser
|
||||
// and AST, since details such as the placement of comments and newlines
|
||||
// are preserved when unchanged.
|
||||
//
|
||||
// The hclwrite API follows a similar principle to XML/HTML DOM, allowing nodes
|
||||
// to be read out, created and inserted, etc. Nodes represent syntax constructs
|
||||
// rather than semantic concepts.
|
||||
package hclwrite
|
463
vendor/github.com/hashicorp/hcl/v2/hclwrite/format.go
generated
vendored
Normal file
463
vendor/github.com/hashicorp/hcl/v2/hclwrite/format.go
generated
vendored
Normal file
@ -0,0 +1,463 @@
|
||||
package hclwrite
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
)
|
||||
|
||||
var inKeyword = hclsyntax.Keyword([]byte{'i', 'n'})
|
||||
|
||||
// placeholder token used when we don't have a token but we don't want
|
||||
// to pass a real "nil" and complicate things with nil pointer checks
|
||||
var nilToken = &Token{
|
||||
Type: hclsyntax.TokenNil,
|
||||
Bytes: []byte{},
|
||||
SpacesBefore: 0,
|
||||
}
|
||||
|
||||
// format rewrites tokens within the given sequence, in-place, to adjust the
|
||||
// whitespace around their content to achieve canonical formatting.
|
||||
func format(tokens Tokens) {
|
||||
// Formatting is a multi-pass process. More details on the passes below,
|
||||
// but this is the overview:
|
||||
// - adjust the leading space on each line to create appropriate
|
||||
// indentation
|
||||
// - adjust spaces between tokens in a single cell using a set of rules
|
||||
// - adjust the leading space in the "assign" and "comment" cells on each
|
||||
// line to vertically align with neighboring lines.
|
||||
// All of these steps operate in-place on the given tokens, so a caller
|
||||
// may collect a flat sequence of all of the tokens underlying an AST
|
||||
// and pass it here and we will then indirectly modify the AST itself.
|
||||
// Formatting must change only whitespace. Specifically, that means
|
||||
// changing the SpacesBefore attribute on a token while leaving the
|
||||
// other token attributes unchanged.
|
||||
|
||||
lines := linesForFormat(tokens)
|
||||
formatIndent(lines)
|
||||
formatSpaces(lines)
|
||||
formatCells(lines)
|
||||
}
|
||||
|
||||
func formatIndent(lines []formatLine) {
|
||||
// Our methodology for indents is to take the input one line at a time
|
||||
// and count the bracketing delimiters on each line. If a line has a net
|
||||
// increase in open brackets, we increase the indent level by one and
|
||||
// remember how many new openers we had. If the line has a net _decrease_,
|
||||
// we'll compare it to the most recent number of openers and decrease the
|
||||
// dedent level by one each time we pass an indent level remembered
|
||||
// earlier.
|
||||
// The "indent stack" used here allows for us to recognize degenerate
|
||||
// input where brackets are not symmetrical within lines and avoid
|
||||
// pushing things too far left or right, creating confusion.
|
||||
|
||||
// We'll start our indent stack at a reasonable capacity to minimize the
|
||||
// chance of us needing to grow it; 10 here means 10 levels of indent,
|
||||
// which should be more than enough for reasonable HCL uses.
|
||||
indents := make([]int, 0, 10)
|
||||
|
||||
for i := range lines {
|
||||
line := &lines[i]
|
||||
if len(line.lead) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if line.lead[0].Type == hclsyntax.TokenNewline {
|
||||
// Never place spaces before a newline
|
||||
line.lead[0].SpacesBefore = 0
|
||||
continue
|
||||
}
|
||||
|
||||
netBrackets := 0
|
||||
for _, token := range line.lead {
|
||||
netBrackets += tokenBracketChange(token)
|
||||
if token.Type == hclsyntax.TokenOHeredoc {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, token := range line.assign {
|
||||
netBrackets += tokenBracketChange(token)
|
||||
}
|
||||
|
||||
switch {
|
||||
case netBrackets > 0:
|
||||
line.lead[0].SpacesBefore = 2 * len(indents)
|
||||
indents = append(indents, netBrackets)
|
||||
case netBrackets < 0:
|
||||
closed := -netBrackets
|
||||
for closed > 0 && len(indents) > 0 {
|
||||
switch {
|
||||
|
||||
case closed > indents[len(indents)-1]:
|
||||
closed -= indents[len(indents)-1]
|
||||
indents = indents[:len(indents)-1]
|
||||
|
||||
case closed < indents[len(indents)-1]:
|
||||
indents[len(indents)-1] -= closed
|
||||
closed = 0
|
||||
|
||||
default:
|
||||
indents = indents[:len(indents)-1]
|
||||
closed = 0
|
||||
}
|
||||
}
|
||||
line.lead[0].SpacesBefore = 2 * len(indents)
|
||||
default:
|
||||
line.lead[0].SpacesBefore = 2 * len(indents)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatSpaces(lines []formatLine) {
|
||||
for _, line := range lines {
|
||||
for i, token := range line.lead {
|
||||
var before, after *Token
|
||||
if i > 0 {
|
||||
before = line.lead[i-1]
|
||||
} else {
|
||||
before = nilToken
|
||||
}
|
||||
if i < (len(line.lead) - 1) {
|
||||
after = line.lead[i+1]
|
||||
} else {
|
||||
after = nilToken
|
||||
}
|
||||
if spaceAfterToken(token, before, after) {
|
||||
after.SpacesBefore = 1
|
||||
} else {
|
||||
after.SpacesBefore = 0
|
||||
}
|
||||
}
|
||||
for i, token := range line.assign {
|
||||
if i == 0 {
|
||||
// first token in "assign" always has one space before to
|
||||
// separate the equals sign from what it's assigning.
|
||||
token.SpacesBefore = 1
|
||||
}
|
||||
|
||||
var before, after *Token
|
||||
if i > 0 {
|
||||
before = line.assign[i-1]
|
||||
} else {
|
||||
before = nilToken
|
||||
}
|
||||
if i < (len(line.assign) - 1) {
|
||||
after = line.assign[i+1]
|
||||
} else {
|
||||
after = nilToken
|
||||
}
|
||||
if spaceAfterToken(token, before, after) {
|
||||
after.SpacesBefore = 1
|
||||
} else {
|
||||
after.SpacesBefore = 0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func formatCells(lines []formatLine) {
|
||||
|
||||
chainStart := -1
|
||||
maxColumns := 0
|
||||
|
||||
// We'll deal with the "assign" cell first, since moving that will
|
||||
// also impact the "comment" cell.
|
||||
closeAssignChain := func(i int) {
|
||||
for _, chainLine := range lines[chainStart:i] {
|
||||
columns := chainLine.lead.Columns()
|
||||
spaces := (maxColumns - columns) + 1
|
||||
chainLine.assign[0].SpacesBefore = spaces
|
||||
}
|
||||
chainStart = -1
|
||||
maxColumns = 0
|
||||
}
|
||||
for i, line := range lines {
|
||||
if line.assign == nil {
|
||||
if chainStart != -1 {
|
||||
closeAssignChain(i)
|
||||
}
|
||||
} else {
|
||||
if chainStart == -1 {
|
||||
chainStart = i
|
||||
}
|
||||
columns := line.lead.Columns()
|
||||
if columns > maxColumns {
|
||||
maxColumns = columns
|
||||
}
|
||||
}
|
||||
}
|
||||
if chainStart != -1 {
|
||||
closeAssignChain(len(lines))
|
||||
}
|
||||
|
||||
// Now we'll deal with the comments
|
||||
closeCommentChain := func(i int) {
|
||||
for _, chainLine := range lines[chainStart:i] {
|
||||
columns := chainLine.lead.Columns() + chainLine.assign.Columns()
|
||||
spaces := (maxColumns - columns) + 1
|
||||
chainLine.comment[0].SpacesBefore = spaces
|
||||
}
|
||||
chainStart = -1
|
||||
maxColumns = 0
|
||||
}
|
||||
for i, line := range lines {
|
||||
if line.comment == nil {
|
||||
if chainStart != -1 {
|
||||
closeCommentChain(i)
|
||||
}
|
||||
} else {
|
||||
if chainStart == -1 {
|
||||
chainStart = i
|
||||
}
|
||||
columns := line.lead.Columns() + line.assign.Columns()
|
||||
if columns > maxColumns {
|
||||
maxColumns = columns
|
||||
}
|
||||
}
|
||||
}
|
||||
if chainStart != -1 {
|
||||
closeCommentChain(len(lines))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// spaceAfterToken decides whether a particular subject token should have a
|
||||
// space after it when surrounded by the given before and after tokens.
|
||||
// "before" can be TokenNil, if the subject token is at the start of a sequence.
|
||||
func spaceAfterToken(subject, before, after *Token) bool {
|
||||
switch {
|
||||
|
||||
case after.Type == hclsyntax.TokenNewline || after.Type == hclsyntax.TokenNil:
|
||||
// Never add spaces before a newline
|
||||
return false
|
||||
|
||||
case subject.Type == hclsyntax.TokenIdent && after.Type == hclsyntax.TokenOParen:
|
||||
// Don't split a function name from open paren in a call
|
||||
return false
|
||||
|
||||
case subject.Type == hclsyntax.TokenDot || after.Type == hclsyntax.TokenDot:
|
||||
// Don't use spaces around attribute access dots
|
||||
return false
|
||||
|
||||
case after.Type == hclsyntax.TokenComma || after.Type == hclsyntax.TokenEllipsis:
|
||||
// No space right before a comma or ... in an argument list
|
||||
return false
|
||||
|
||||
case subject.Type == hclsyntax.TokenComma:
|
||||
// Always a space after a comma
|
||||
return true
|
||||
|
||||
case subject.Type == hclsyntax.TokenQuotedLit || subject.Type == hclsyntax.TokenStringLit || subject.Type == hclsyntax.TokenOQuote || subject.Type == hclsyntax.TokenOHeredoc || after.Type == hclsyntax.TokenQuotedLit || after.Type == hclsyntax.TokenStringLit || after.Type == hclsyntax.TokenCQuote || after.Type == hclsyntax.TokenCHeredoc:
|
||||
// No extra spaces within templates
|
||||
return false
|
||||
|
||||
case inKeyword.TokenMatches(subject.asHCLSyntax()) && before.Type == hclsyntax.TokenIdent:
|
||||
// This is a special case for inside for expressions where a user
|
||||
// might want to use a literal tuple constructor:
|
||||
// [for x in [foo]: x]
|
||||
// ... in that case, we would normally produce in[foo] thinking that
|
||||
// in is a reference, but we'll recognize it as a keyword here instead
|
||||
// to make the result less confusing.
|
||||
return true
|
||||
|
||||
case after.Type == hclsyntax.TokenOBrack && (subject.Type == hclsyntax.TokenIdent || subject.Type == hclsyntax.TokenNumberLit || tokenBracketChange(subject) < 0):
|
||||
return false
|
||||
|
||||
case subject.Type == hclsyntax.TokenMinus:
|
||||
// Since a minus can either be subtraction or negation, and the latter
|
||||
// should _not_ have a space after it, we need to use some heuristics
|
||||
// to decide which case this is.
|
||||
// We guess that we have a negation if the token before doesn't look
|
||||
// like it could be the end of an expression.
|
||||
|
||||
switch before.Type {
|
||||
|
||||
case hclsyntax.TokenNil:
|
||||
// Minus at the start of input must be a negation
|
||||
return false
|
||||
|
||||
case hclsyntax.TokenOParen, hclsyntax.TokenOBrace, hclsyntax.TokenOBrack, hclsyntax.TokenEqual, hclsyntax.TokenColon, hclsyntax.TokenComma, hclsyntax.TokenQuestion:
|
||||
// Minus immediately after an opening bracket or separator must be a negation.
|
||||
return false
|
||||
|
||||
case hclsyntax.TokenPlus, hclsyntax.TokenStar, hclsyntax.TokenSlash, hclsyntax.TokenPercent, hclsyntax.TokenMinus:
|
||||
// Minus immediately after another arithmetic operator must be negation.
|
||||
return false
|
||||
|
||||
case hclsyntax.TokenEqualOp, hclsyntax.TokenNotEqual, hclsyntax.TokenGreaterThan, hclsyntax.TokenGreaterThanEq, hclsyntax.TokenLessThan, hclsyntax.TokenLessThanEq:
|
||||
// Minus immediately after another comparison operator must be negation.
|
||||
return false
|
||||
|
||||
case hclsyntax.TokenAnd, hclsyntax.TokenOr, hclsyntax.TokenBang:
|
||||
// Minus immediately after logical operator doesn't make sense but probably intended as negation.
|
||||
return false
|
||||
|
||||
default:
|
||||
return true
|
||||
}
|
||||
|
||||
case subject.Type == hclsyntax.TokenOBrace || after.Type == hclsyntax.TokenCBrace:
|
||||
// Unlike other bracket types, braces have spaces on both sides of them,
|
||||
// both in single-line nested blocks foo { bar = baz } and in object
|
||||
// constructor expressions foo = { bar = baz }.
|
||||
if subject.Type == hclsyntax.TokenOBrace && after.Type == hclsyntax.TokenCBrace {
|
||||
// An open brace followed by a close brace is an exception, however.
|
||||
// e.g. foo {} rather than foo { }
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
||||
// In the unlikely event that an interpolation expression is just
|
||||
// a single object constructor, we'll put a space between the ${ and
|
||||
// the following { to make this more obvious, and then the same
|
||||
// thing for the two braces at the end.
|
||||
case (subject.Type == hclsyntax.TokenTemplateInterp || subject.Type == hclsyntax.TokenTemplateControl) && after.Type == hclsyntax.TokenOBrace:
|
||||
return true
|
||||
case subject.Type == hclsyntax.TokenCBrace && after.Type == hclsyntax.TokenTemplateSeqEnd:
|
||||
return true
|
||||
|
||||
// Don't add spaces between interpolated items
|
||||
case subject.Type == hclsyntax.TokenTemplateSeqEnd && (after.Type == hclsyntax.TokenTemplateInterp || after.Type == hclsyntax.TokenTemplateControl):
|
||||
return false
|
||||
|
||||
case tokenBracketChange(subject) > 0:
|
||||
// No spaces after open brackets
|
||||
return false
|
||||
|
||||
case tokenBracketChange(after) < 0:
|
||||
// No spaces before close brackets
|
||||
return false
|
||||
|
||||
default:
|
||||
// Most tokens are space-separated
|
||||
return true
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func linesForFormat(tokens Tokens) []formatLine {
|
||||
if len(tokens) == 0 {
|
||||
return make([]formatLine, 0)
|
||||
}
|
||||
|
||||
// first we'll count our lines, so we can allocate the array for them in
|
||||
// a single block. (We want to minimize memory pressure in this codepath,
|
||||
// so it can be run somewhat-frequently by editor integrations.)
|
||||
lineCount := 1 // if there are zero newlines then there is one line
|
||||
for _, tok := range tokens {
|
||||
if tokenIsNewline(tok) {
|
||||
lineCount++
|
||||
}
|
||||
}
|
||||
|
||||
// To start, we'll just put everything in the "lead" cell on each line,
|
||||
// and then do another pass over the lines afterwards to adjust.
|
||||
lines := make([]formatLine, lineCount)
|
||||
li := 0
|
||||
lineStart := 0
|
||||
for i, tok := range tokens {
|
||||
if tok.Type == hclsyntax.TokenEOF {
|
||||
// The EOF token doesn't belong to any line, and terminates the
|
||||
// token sequence.
|
||||
lines[li].lead = tokens[lineStart:i]
|
||||
break
|
||||
}
|
||||
|
||||
if tokenIsNewline(tok) {
|
||||
lines[li].lead = tokens[lineStart : i+1]
|
||||
lineStart = i + 1
|
||||
li++
|
||||
}
|
||||
}
|
||||
|
||||
// If a set of tokens doesn't end in TokenEOF (e.g. because it's a
|
||||
// fragment of tokens from the middle of a file) then we might fall
|
||||
// out here with a line still pending.
|
||||
if lineStart < len(tokens) {
|
||||
lines[li].lead = tokens[lineStart:]
|
||||
if lines[li].lead[len(lines[li].lead)-1].Type == hclsyntax.TokenEOF {
|
||||
lines[li].lead = lines[li].lead[:len(lines[li].lead)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// Now we'll pick off any trailing comments and attribute assignments
|
||||
// to shuffle off into the "comment" and "assign" cells.
|
||||
for i := range lines {
|
||||
line := &lines[i]
|
||||
|
||||
if len(line.lead) == 0 {
|
||||
// if the line is empty then there's nothing for us to do
|
||||
// (this should happen only for the final line, because all other
|
||||
// lines would have a newline token of some kind)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(line.lead) > 1 && line.lead[len(line.lead)-1].Type == hclsyntax.TokenComment {
|
||||
line.comment = line.lead[len(line.lead)-1:]
|
||||
line.lead = line.lead[:len(line.lead)-1]
|
||||
}
|
||||
|
||||
for i, tok := range line.lead {
|
||||
if i > 0 && tok.Type == hclsyntax.TokenEqual {
|
||||
// We only move the tokens into "assign" if the RHS seems to
|
||||
// be a whole expression, which we determine by counting
|
||||
// brackets. If there's a net positive number of brackets
|
||||
// then that suggests we're introducing a multi-line expression.
|
||||
netBrackets := 0
|
||||
for _, token := range line.lead[i:] {
|
||||
netBrackets += tokenBracketChange(token)
|
||||
}
|
||||
|
||||
if netBrackets == 0 {
|
||||
line.assign = line.lead[i:]
|
||||
line.lead = line.lead[:i]
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func tokenIsNewline(tok *Token) bool {
|
||||
if tok.Type == hclsyntax.TokenNewline {
|
||||
return true
|
||||
} else if tok.Type == hclsyntax.TokenComment {
|
||||
// Single line tokens (# and //) consume their terminating newline,
|
||||
// so we need to treat them as newline tokens as well.
|
||||
if len(tok.Bytes) > 0 && tok.Bytes[len(tok.Bytes)-1] == '\n' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func tokenBracketChange(tok *Token) int {
|
||||
switch tok.Type {
|
||||
case hclsyntax.TokenOBrace, hclsyntax.TokenOBrack, hclsyntax.TokenOParen, hclsyntax.TokenTemplateControl, hclsyntax.TokenTemplateInterp:
|
||||
return 1
|
||||
case hclsyntax.TokenCBrace, hclsyntax.TokenCBrack, hclsyntax.TokenCParen, hclsyntax.TokenTemplateSeqEnd:
|
||||
return -1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// formatLine represents a single line of source code for formatting purposes,
|
||||
// splitting its tokens into up to three "cells":
|
||||
//
|
||||
// lead: always present, representing everything up to one of the others
|
||||
// assign: if line contains an attribute assignment, represents the tokens
|
||||
// starting at (and including) the equals symbol
|
||||
// comment: if line contains any non-comment tokens and ends with a
|
||||
// single-line comment token, represents the comment.
|
||||
//
|
||||
// When formatting, the leading spaces of the first tokens in each of these
|
||||
// cells is adjusted to align vertically their occurences on consecutive
|
||||
// rows.
|
||||
type formatLine struct {
|
||||
lead Tokens
|
||||
assign Tokens
|
||||
comment Tokens
|
||||
}
|
252
vendor/github.com/hashicorp/hcl/v2/hclwrite/generate.go
generated
vendored
Normal file
252
vendor/github.com/hashicorp/hcl/v2/hclwrite/generate.go
generated
vendored
Normal file
@ -0,0 +1,252 @@
|
||||
package hclwrite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// TokensForValue returns a sequence of tokens that represents the given
|
||||
// constant value.
|
||||
//
|
||||
// This function only supports types that are used by HCL. In particular, it
|
||||
// does not support capsule types and will panic if given one.
|
||||
//
|
||||
// It is not possible to express an unknown value in source code, so this
|
||||
// function will panic if the given value is unknown or contains any unknown
|
||||
// values. A caller can call the value's IsWhollyKnown method to verify that
|
||||
// no unknown values are present before calling TokensForValue.
|
||||
func TokensForValue(val cty.Value) Tokens {
|
||||
toks := appendTokensForValue(val, nil)
|
||||
format(toks) // fiddle with the SpacesBefore field to get canonical spacing
|
||||
return toks
|
||||
}
|
||||
|
||||
// TokensForTraversal returns a sequence of tokens that represents the given
|
||||
// traversal.
|
||||
//
|
||||
// If the traversal is absolute then the result is a self-contained, valid
|
||||
// reference expression. If the traversal is relative then the returned tokens
|
||||
// could be appended to some other expression tokens to traverse into the
|
||||
// represented expression.
|
||||
func TokensForTraversal(traversal hcl.Traversal) Tokens {
|
||||
toks := appendTokensForTraversal(traversal, nil)
|
||||
format(toks) // fiddle with the SpacesBefore field to get canonical spacing
|
||||
return toks
|
||||
}
|
||||
|
||||
func appendTokensForValue(val cty.Value, toks Tokens) Tokens {
|
||||
switch {
|
||||
|
||||
case !val.IsKnown():
|
||||
panic("cannot produce tokens for unknown value")
|
||||
|
||||
case val.IsNull():
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenIdent,
|
||||
Bytes: []byte(`null`),
|
||||
})
|
||||
|
||||
case val.Type() == cty.Bool:
|
||||
var src []byte
|
||||
if val.True() {
|
||||
src = []byte(`true`)
|
||||
} else {
|
||||
src = []byte(`false`)
|
||||
}
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenIdent,
|
||||
Bytes: src,
|
||||
})
|
||||
|
||||
case val.Type() == cty.Number:
|
||||
bf := val.AsBigFloat()
|
||||
srcStr := bf.Text('f', -1)
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenNumberLit,
|
||||
Bytes: []byte(srcStr),
|
||||
})
|
||||
|
||||
case val.Type() == cty.String:
|
||||
// TODO: If it's a multi-line string ending in a newline, format
|
||||
// it as a HEREDOC instead.
|
||||
src := escapeQuotedStringLit(val.AsString())
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenOQuote,
|
||||
Bytes: []byte{'"'},
|
||||
})
|
||||
if len(src) > 0 {
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenQuotedLit,
|
||||
Bytes: src,
|
||||
})
|
||||
}
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenCQuote,
|
||||
Bytes: []byte{'"'},
|
||||
})
|
||||
|
||||
case val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType():
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenOBrack,
|
||||
Bytes: []byte{'['},
|
||||
})
|
||||
|
||||
i := 0
|
||||
for it := val.ElementIterator(); it.Next(); {
|
||||
if i > 0 {
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenComma,
|
||||
Bytes: []byte{','},
|
||||
})
|
||||
}
|
||||
_, eVal := it.Element()
|
||||
toks = appendTokensForValue(eVal, toks)
|
||||
i++
|
||||
}
|
||||
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenCBrack,
|
||||
Bytes: []byte{']'},
|
||||
})
|
||||
|
||||
case val.Type().IsMapType() || val.Type().IsObjectType():
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenOBrace,
|
||||
Bytes: []byte{'{'},
|
||||
})
|
||||
|
||||
i := 0
|
||||
for it := val.ElementIterator(); it.Next(); {
|
||||
if i > 0 {
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenComma,
|
||||
Bytes: []byte{','},
|
||||
})
|
||||
}
|
||||
eKey, eVal := it.Element()
|
||||
if hclsyntax.ValidIdentifier(eKey.AsString()) {
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenIdent,
|
||||
Bytes: []byte(eKey.AsString()),
|
||||
})
|
||||
} else {
|
||||
toks = appendTokensForValue(eKey, toks)
|
||||
}
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenEqual,
|
||||
Bytes: []byte{'='},
|
||||
})
|
||||
toks = appendTokensForValue(eVal, toks)
|
||||
i++
|
||||
}
|
||||
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenCBrace,
|
||||
Bytes: []byte{'}'},
|
||||
})
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("cannot produce tokens for %#v", val))
|
||||
}
|
||||
|
||||
return toks
|
||||
}
|
||||
|
||||
func appendTokensForTraversal(traversal hcl.Traversal, toks Tokens) Tokens {
|
||||
for _, step := range traversal {
|
||||
toks = appendTokensForTraversalStep(step, toks)
|
||||
}
|
||||
return toks
|
||||
}
|
||||
|
||||
func appendTokensForTraversalStep(step hcl.Traverser, toks Tokens) Tokens {
|
||||
switch ts := step.(type) {
|
||||
case hcl.TraverseRoot:
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenIdent,
|
||||
Bytes: []byte(ts.Name),
|
||||
})
|
||||
case hcl.TraverseAttr:
|
||||
toks = append(
|
||||
toks,
|
||||
&Token{
|
||||
Type: hclsyntax.TokenDot,
|
||||
Bytes: []byte{'.'},
|
||||
},
|
||||
&Token{
|
||||
Type: hclsyntax.TokenIdent,
|
||||
Bytes: []byte(ts.Name),
|
||||
},
|
||||
)
|
||||
case hcl.TraverseIndex:
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenOBrack,
|
||||
Bytes: []byte{'['},
|
||||
})
|
||||
toks = appendTokensForValue(ts.Key, toks)
|
||||
toks = append(toks, &Token{
|
||||
Type: hclsyntax.TokenCBrack,
|
||||
Bytes: []byte{']'},
|
||||
})
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported traversal step type %T", step))
|
||||
}
|
||||
|
||||
return toks
|
||||
}
|
||||
|
||||
func escapeQuotedStringLit(s string) []byte {
|
||||
if len(s) == 0 {
|
||||
return nil
|
||||
}
|
||||
buf := make([]byte, 0, len(s))
|
||||
for i, r := range s {
|
||||
switch r {
|
||||
case '\n':
|
||||
buf = append(buf, '\\', 'n')
|
||||
case '\r':
|
||||
buf = append(buf, '\\', 'r')
|
||||
case '\t':
|
||||
buf = append(buf, '\\', 't')
|
||||
case '"':
|
||||
buf = append(buf, '\\', '"')
|
||||
case '\\':
|
||||
buf = append(buf, '\\', '\\')
|
||||
case '$', '%':
|
||||
buf = appendRune(buf, r)
|
||||
remain := s[i+1:]
|
||||
if len(remain) > 0 && remain[0] == '{' {
|
||||
// Double up our template introducer symbol to escape it.
|
||||
buf = appendRune(buf, r)
|
||||
}
|
||||
default:
|
||||
if !unicode.IsPrint(r) {
|
||||
var fmted string
|
||||
if r < 65536 {
|
||||
fmted = fmt.Sprintf("\\u%04x", r)
|
||||
} else {
|
||||
fmted = fmt.Sprintf("\\U%08x", r)
|
||||
}
|
||||
buf = append(buf, fmted...)
|
||||
} else {
|
||||
buf = appendRune(buf, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func appendRune(b []byte, r rune) []byte {
|
||||
l := utf8.RuneLen(r)
|
||||
for i := 0; i < l; i++ {
|
||||
b = append(b, 0) // make room at the end of our buffer
|
||||
}
|
||||
ch := b[len(b)-l:]
|
||||
utf8.EncodeRune(ch, r)
|
||||
return b
|
||||
}
|
23
vendor/github.com/hashicorp/hcl/v2/hclwrite/native_node_sorter.go
generated
vendored
Normal file
23
vendor/github.com/hashicorp/hcl/v2/hclwrite/native_node_sorter.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
package hclwrite
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
)
|
||||
|
||||
type nativeNodeSorter struct {
|
||||
Nodes []hclsyntax.Node
|
||||
}
|
||||
|
||||
func (s nativeNodeSorter) Len() int {
|
||||
return len(s.Nodes)
|
||||
}
|
||||
|
||||
func (s nativeNodeSorter) Less(i, j int) bool {
|
||||
rangeI := s.Nodes[i].Range()
|
||||
rangeJ := s.Nodes[j].Range()
|
||||
return rangeI.Start.Byte < rangeJ.Start.Byte
|
||||
}
|
||||
|
||||
func (s nativeNodeSorter) Swap(i, j int) {
|
||||
s.Nodes[i], s.Nodes[j] = s.Nodes[j], s.Nodes[i]
|
||||
}
|
260
vendor/github.com/hashicorp/hcl/v2/hclwrite/node.go
generated
vendored
Normal file
260
vendor/github.com/hashicorp/hcl/v2/hclwrite/node.go
generated
vendored
Normal file
@ -0,0 +1,260 @@
|
||||
package hclwrite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
// node represents a node in the AST.
|
||||
type node struct {
|
||||
content nodeContent
|
||||
|
||||
list *nodes
|
||||
before, after *node
|
||||
}
|
||||
|
||||
func newNode(c nodeContent) *node {
|
||||
return &node{
|
||||
content: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) Equal(other *node) bool {
|
||||
return cmp.Equal(n.content, other.content)
|
||||
}
|
||||
|
||||
func (n *node) BuildTokens(to Tokens) Tokens {
|
||||
return n.content.BuildTokens(to)
|
||||
}
|
||||
|
||||
// Detach removes the receiver from the list it currently belongs to. If the
|
||||
// node is not currently in a list, this is a no-op.
|
||||
func (n *node) Detach() {
|
||||
if n.list == nil {
|
||||
return
|
||||
}
|
||||
if n.before != nil {
|
||||
n.before.after = n.after
|
||||
}
|
||||
if n.after != nil {
|
||||
n.after.before = n.before
|
||||
}
|
||||
if n.list.first == n {
|
||||
n.list.first = n.after
|
||||
}
|
||||
if n.list.last == n {
|
||||
n.list.last = n.before
|
||||
}
|
||||
n.list = nil
|
||||
n.before = nil
|
||||
n.after = nil
|
||||
}
|
||||
|
||||
// ReplaceWith removes the receiver from the list it currently belongs to and
|
||||
// inserts a new node with the given content in its place. If the node is not
|
||||
// currently in a list, this function will panic.
|
||||
//
|
||||
// The return value is the newly-constructed node, containing the given content.
|
||||
// After this function returns, the reciever is no longer attached to a list.
|
||||
func (n *node) ReplaceWith(c nodeContent) *node {
|
||||
if n.list == nil {
|
||||
panic("can't replace node that is not in a list")
|
||||
}
|
||||
|
||||
before := n.before
|
||||
after := n.after
|
||||
list := n.list
|
||||
n.before, n.after, n.list = nil, nil, nil
|
||||
|
||||
nn := newNode(c)
|
||||
nn.before = before
|
||||
nn.after = after
|
||||
nn.list = list
|
||||
if before != nil {
|
||||
before.after = nn
|
||||
}
|
||||
if after != nil {
|
||||
after.before = nn
|
||||
}
|
||||
return nn
|
||||
}
|
||||
|
||||
func (n *node) assertUnattached() {
|
||||
if n.list != nil {
|
||||
panic(fmt.Sprintf("attempt to attach already-attached node %#v", n))
|
||||
}
|
||||
}
|
||||
|
||||
// nodeContent is the interface type implemented by all AST content types.
|
||||
type nodeContent interface {
|
||||
walkChildNodes(w internalWalkFunc)
|
||||
BuildTokens(to Tokens) Tokens
|
||||
}
|
||||
|
||||
// nodes is a list of nodes.
|
||||
type nodes struct {
|
||||
first, last *node
|
||||
}
|
||||
|
||||
func (ns *nodes) BuildTokens(to Tokens) Tokens {
|
||||
for n := ns.first; n != nil; n = n.after {
|
||||
to = n.BuildTokens(to)
|
||||
}
|
||||
return to
|
||||
}
|
||||
|
||||
func (ns *nodes) Clear() {
|
||||
ns.first = nil
|
||||
ns.last = nil
|
||||
}
|
||||
|
||||
func (ns *nodes) Append(c nodeContent) *node {
|
||||
n := &node{
|
||||
content: c,
|
||||
}
|
||||
ns.AppendNode(n)
|
||||
n.list = ns
|
||||
return n
|
||||
}
|
||||
|
||||
func (ns *nodes) AppendNode(n *node) {
|
||||
if ns.last != nil {
|
||||
n.before = ns.last
|
||||
ns.last.after = n
|
||||
}
|
||||
n.list = ns
|
||||
ns.last = n
|
||||
if ns.first == nil {
|
||||
ns.first = n
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *nodes) AppendUnstructuredTokens(tokens Tokens) *node {
|
||||
if len(tokens) == 0 {
|
||||
return nil
|
||||
}
|
||||
n := newNode(tokens)
|
||||
ns.AppendNode(n)
|
||||
n.list = ns
|
||||
return n
|
||||
}
|
||||
|
||||
// FindNodeWithContent searches the nodes for a node whose content equals
|
||||
// the given content. If it finds one then it returns it. Otherwise it returns
|
||||
// nil.
|
||||
func (ns *nodes) FindNodeWithContent(content nodeContent) *node {
|
||||
for n := ns.first; n != nil; n = n.after {
|
||||
if n.content == content {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// nodeSet is an unordered set of nodes. It is used to describe a set of nodes
|
||||
// that all belong to the same list that have some role or characteristic
|
||||
// in common.
|
||||
type nodeSet map[*node]struct{}
|
||||
|
||||
func newNodeSet() nodeSet {
|
||||
return make(nodeSet)
|
||||
}
|
||||
|
||||
func (ns nodeSet) Has(n *node) bool {
|
||||
if ns == nil {
|
||||
return false
|
||||
}
|
||||
_, exists := ns[n]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (ns nodeSet) Add(n *node) {
|
||||
ns[n] = struct{}{}
|
||||
}
|
||||
|
||||
func (ns nodeSet) Remove(n *node) {
|
||||
delete(ns, n)
|
||||
}
|
||||
|
||||
func (ns nodeSet) List() []*node {
|
||||
if len(ns) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := make([]*node, 0, len(ns))
|
||||
|
||||
// Determine which list we are working with. We assume here that all of
|
||||
// the nodes belong to the same list, since that is part of the contract
|
||||
// for nodeSet.
|
||||
var list *nodes
|
||||
for n := range ns {
|
||||
list = n.list
|
||||
break
|
||||
}
|
||||
|
||||
// We recover the order by iterating over the whole list. This is not
|
||||
// the most efficient way to do it, but our node lists should always be
|
||||
// small so not worth making things more complex.
|
||||
for n := list.first; n != nil; n = n.after {
|
||||
if ns.Has(n) {
|
||||
ret = append(ret, n)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// FindNodeWithContent searches the nodes for a node whose content equals
|
||||
// the given content. If it finds one then it returns it. Otherwise it returns
|
||||
// nil.
|
||||
func (ns nodeSet) FindNodeWithContent(content nodeContent) *node {
|
||||
for n := range ns {
|
||||
if n.content == content {
|
||||
return n
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type internalWalkFunc func(*node)
|
||||
|
||||
// inTree can be embedded into a content struct that has child nodes to get
|
||||
// a standard implementation of the NodeContent interface and a record of
|
||||
// a potential parent node.
|
||||
type inTree struct {
|
||||
parent *node
|
||||
children *nodes
|
||||
}
|
||||
|
||||
func newInTree() inTree {
|
||||
return inTree{
|
||||
children: &nodes{},
|
||||
}
|
||||
}
|
||||
|
||||
func (it *inTree) assertUnattached() {
|
||||
if it.parent != nil {
|
||||
panic(fmt.Sprintf("node is already attached to %T", it.parent.content))
|
||||
}
|
||||
}
|
||||
|
||||
func (it *inTree) walkChildNodes(w internalWalkFunc) {
|
||||
for n := it.children.first; n != nil; n = n.after {
|
||||
w(n)
|
||||
}
|
||||
}
|
||||
|
||||
func (it *inTree) BuildTokens(to Tokens) Tokens {
|
||||
for n := it.children.first; n != nil; n = n.after {
|
||||
to = n.BuildTokens(to)
|
||||
}
|
||||
return to
|
||||
}
|
||||
|
||||
// leafNode can be embedded into a content struct to give it a do-nothing
|
||||
// implementation of walkChildNodes
|
||||
type leafNode struct {
|
||||
}
|
||||
|
||||
func (n *leafNode) walkChildNodes(w internalWalkFunc) {
|
||||
}
|
599
vendor/github.com/hashicorp/hcl/v2/hclwrite/parser.go
generated
vendored
Normal file
599
vendor/github.com/hashicorp/hcl/v2/hclwrite/parser.go
generated
vendored
Normal file
@ -0,0 +1,599 @@
|
||||
package hclwrite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// Our "parser" here is actually not doing any parsing of its own. Instead,
|
||||
// it leans on the native parser in hclsyntax, and then uses the source ranges
|
||||
// from the AST to partition the raw token sequence to match the raw tokens
|
||||
// up to AST nodes.
|
||||
//
|
||||
// This strategy feels somewhat counter-intuitive, since most of the work the
|
||||
// parser does is thrown away here, but this strategy is chosen because the
|
||||
// normal parsing work done by hclsyntax is considered to be the "main case",
|
||||
// while modifying and re-printing source is more of an edge case, used only
|
||||
// in ancillary tools, and so it's good to keep all the main parsing logic
|
||||
// with the main case but keep all of the extra complexity of token wrangling
|
||||
// out of the main parser, which is already rather complex just serving the
|
||||
// use-cases it already serves.
|
||||
//
|
||||
// If the parsing step produces any errors, the returned File is nil because
|
||||
// we can't reliably extract tokens from the partial AST produced by an
|
||||
// erroneous parse.
|
||||
func parse(src []byte, filename string, start hcl.Pos) (*File, hcl.Diagnostics) {
|
||||
file, diags := hclsyntax.ParseConfig(src, filename, start)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
// To do our work here, we use the "native" tokens (those from hclsyntax)
|
||||
// to match against source ranges in the AST, but ultimately produce
|
||||
// slices from our sequence of "writer" tokens, which contain only
|
||||
// *relative* position information that is more appropriate for
|
||||
// transformation/writing use-cases.
|
||||
nativeTokens, diags := hclsyntax.LexConfig(src, filename, start)
|
||||
if diags.HasErrors() {
|
||||
// should never happen, since we would've caught these diags in
|
||||
// the first call above.
|
||||
return nil, diags
|
||||
}
|
||||
writerTokens := writerTokens(nativeTokens)
|
||||
|
||||
from := inputTokens{
|
||||
nativeTokens: nativeTokens,
|
||||
writerTokens: writerTokens,
|
||||
}
|
||||
|
||||
before, root, after := parseBody(file.Body.(*hclsyntax.Body), from)
|
||||
ret := &File{
|
||||
inTree: newInTree(),
|
||||
|
||||
srcBytes: src,
|
||||
body: root,
|
||||
}
|
||||
|
||||
nodes := ret.inTree.children
|
||||
nodes.Append(before.Tokens())
|
||||
nodes.AppendNode(root)
|
||||
nodes.Append(after.Tokens())
|
||||
|
||||
return ret, diags
|
||||
}
|
||||
|
||||
type inputTokens struct {
|
||||
nativeTokens hclsyntax.Tokens
|
||||
writerTokens Tokens
|
||||
}
|
||||
|
||||
func (it inputTokens) Partition(rng hcl.Range) (before, within, after inputTokens) {
|
||||
start, end := partitionTokens(it.nativeTokens, rng)
|
||||
before = it.Slice(0, start)
|
||||
within = it.Slice(start, end)
|
||||
after = it.Slice(end, len(it.nativeTokens))
|
||||
return
|
||||
}
|
||||
|
||||
func (it inputTokens) PartitionType(ty hclsyntax.TokenType) (before, within, after inputTokens) {
|
||||
for i, t := range it.writerTokens {
|
||||
if t.Type == ty {
|
||||
return it.Slice(0, i), it.Slice(i, i+1), it.Slice(i+1, len(it.nativeTokens))
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf("didn't find any token of type %s", ty))
|
||||
}
|
||||
|
||||
func (it inputTokens) PartitionTypeSingle(ty hclsyntax.TokenType) (before inputTokens, found *Token, after inputTokens) {
|
||||
before, within, after := it.PartitionType(ty)
|
||||
if within.Len() != 1 {
|
||||
panic("PartitionType found more than one token")
|
||||
}
|
||||
return before, within.Tokens()[0], after
|
||||
}
|
||||
|
||||
// PartitionIncludeComments is like Partition except the returned "within"
|
||||
// range includes any lead and line comments associated with the range.
|
||||
func (it inputTokens) PartitionIncludingComments(rng hcl.Range) (before, within, after inputTokens) {
|
||||
start, end := partitionTokens(it.nativeTokens, rng)
|
||||
start = partitionLeadCommentTokens(it.nativeTokens[:start])
|
||||
_, afterNewline := partitionLineEndTokens(it.nativeTokens[end:])
|
||||
end += afterNewline
|
||||
|
||||
before = it.Slice(0, start)
|
||||
within = it.Slice(start, end)
|
||||
after = it.Slice(end, len(it.nativeTokens))
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// PartitionBlockItem is similar to PartitionIncludeComments but it returns
|
||||
// the comments as separate token sequences so that they can be captured into
|
||||
// AST attributes. It makes assumptions that apply only to block items, so
|
||||
// should not be used for other constructs.
|
||||
func (it inputTokens) PartitionBlockItem(rng hcl.Range) (before, leadComments, within, lineComments, newline, after inputTokens) {
|
||||
before, within, after = it.Partition(rng)
|
||||
before, leadComments = before.PartitionLeadComments()
|
||||
lineComments, newline, after = after.PartitionLineEndTokens()
|
||||
return
|
||||
}
|
||||
|
||||
func (it inputTokens) PartitionLeadComments() (before, within inputTokens) {
|
||||
start := partitionLeadCommentTokens(it.nativeTokens)
|
||||
before = it.Slice(0, start)
|
||||
within = it.Slice(start, len(it.nativeTokens))
|
||||
return
|
||||
}
|
||||
|
||||
func (it inputTokens) PartitionLineEndTokens() (comments, newline, after inputTokens) {
|
||||
afterComments, afterNewline := partitionLineEndTokens(it.nativeTokens)
|
||||
comments = it.Slice(0, afterComments)
|
||||
newline = it.Slice(afterComments, afterNewline)
|
||||
after = it.Slice(afterNewline, len(it.nativeTokens))
|
||||
return
|
||||
}
|
||||
|
||||
func (it inputTokens) Slice(start, end int) inputTokens {
|
||||
// When we slice, we create a new slice with no additional capacity because
|
||||
// we expect that these slices will be mutated in order to insert
|
||||
// new code into the AST, and we want to ensure that a new underlying
|
||||
// array gets allocated in that case, rather than writing into some
|
||||
// following slice and corrupting it.
|
||||
return inputTokens{
|
||||
nativeTokens: it.nativeTokens[start:end:end],
|
||||
writerTokens: it.writerTokens[start:end:end],
|
||||
}
|
||||
}
|
||||
|
||||
func (it inputTokens) Len() int {
|
||||
return len(it.nativeTokens)
|
||||
}
|
||||
|
||||
func (it inputTokens) Tokens() Tokens {
|
||||
return it.writerTokens
|
||||
}
|
||||
|
||||
func (it inputTokens) Types() []hclsyntax.TokenType {
|
||||
ret := make([]hclsyntax.TokenType, len(it.nativeTokens))
|
||||
for i, tok := range it.nativeTokens {
|
||||
ret[i] = tok.Type
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// parseBody locates the given body within the given input tokens and returns
|
||||
// the resulting *Body object as well as the tokens that appeared before and
|
||||
// after it.
|
||||
func parseBody(nativeBody *hclsyntax.Body, from inputTokens) (inputTokens, *node, inputTokens) {
|
||||
before, within, after := from.PartitionIncludingComments(nativeBody.SrcRange)
|
||||
|
||||
// The main AST doesn't retain the original source ordering of the
|
||||
// body items, so we need to reconstruct that ordering by inspecting
|
||||
// their source ranges.
|
||||
nativeItems := make([]hclsyntax.Node, 0, len(nativeBody.Attributes)+len(nativeBody.Blocks))
|
||||
for _, nativeAttr := range nativeBody.Attributes {
|
||||
nativeItems = append(nativeItems, nativeAttr)
|
||||
}
|
||||
for _, nativeBlock := range nativeBody.Blocks {
|
||||
nativeItems = append(nativeItems, nativeBlock)
|
||||
}
|
||||
sort.Sort(nativeNodeSorter{nativeItems})
|
||||
|
||||
body := &Body{
|
||||
inTree: newInTree(),
|
||||
items: newNodeSet(),
|
||||
}
|
||||
|
||||
remain := within
|
||||
for _, nativeItem := range nativeItems {
|
||||
beforeItem, item, afterItem := parseBodyItem(nativeItem, remain)
|
||||
|
||||
if beforeItem.Len() > 0 {
|
||||
body.AppendUnstructuredTokens(beforeItem.Tokens())
|
||||
}
|
||||
body.appendItemNode(item)
|
||||
|
||||
remain = afterItem
|
||||
}
|
||||
|
||||
if remain.Len() > 0 {
|
||||
body.AppendUnstructuredTokens(remain.Tokens())
|
||||
}
|
||||
|
||||
return before, newNode(body), after
|
||||
}
|
||||
|
||||
func parseBodyItem(nativeItem hclsyntax.Node, from inputTokens) (inputTokens, *node, inputTokens) {
|
||||
before, leadComments, within, lineComments, newline, after := from.PartitionBlockItem(nativeItem.Range())
|
||||
|
||||
var item *node
|
||||
|
||||
switch tItem := nativeItem.(type) {
|
||||
case *hclsyntax.Attribute:
|
||||
item = parseAttribute(tItem, within, leadComments, lineComments, newline)
|
||||
case *hclsyntax.Block:
|
||||
item = parseBlock(tItem, within, leadComments, lineComments, newline)
|
||||
default:
|
||||
// should never happen if caller is behaving
|
||||
panic("unsupported native item type")
|
||||
}
|
||||
|
||||
return before, item, after
|
||||
}
|
||||
|
||||
func parseAttribute(nativeAttr *hclsyntax.Attribute, from, leadComments, lineComments, newline inputTokens) *node {
|
||||
attr := &Attribute{
|
||||
inTree: newInTree(),
|
||||
}
|
||||
children := attr.inTree.children
|
||||
|
||||
{
|
||||
cn := newNode(newComments(leadComments.Tokens()))
|
||||
attr.leadComments = cn
|
||||
children.AppendNode(cn)
|
||||
}
|
||||
|
||||
before, nameTokens, from := from.Partition(nativeAttr.NameRange)
|
||||
{
|
||||
children.AppendUnstructuredTokens(before.Tokens())
|
||||
if nameTokens.Len() != 1 {
|
||||
// Should never happen with valid input
|
||||
panic("attribute name is not exactly one token")
|
||||
}
|
||||
token := nameTokens.Tokens()[0]
|
||||
in := newNode(newIdentifier(token))
|
||||
attr.name = in
|
||||
children.AppendNode(in)
|
||||
}
|
||||
|
||||
before, equalsTokens, from := from.Partition(nativeAttr.EqualsRange)
|
||||
children.AppendUnstructuredTokens(before.Tokens())
|
||||
children.AppendUnstructuredTokens(equalsTokens.Tokens())
|
||||
|
||||
before, exprTokens, from := from.Partition(nativeAttr.Expr.Range())
|
||||
{
|
||||
children.AppendUnstructuredTokens(before.Tokens())
|
||||
exprNode := parseExpression(nativeAttr.Expr, exprTokens)
|
||||
attr.expr = exprNode
|
||||
children.AppendNode(exprNode)
|
||||
}
|
||||
|
||||
{
|
||||
cn := newNode(newComments(lineComments.Tokens()))
|
||||
attr.lineComments = cn
|
||||
children.AppendNode(cn)
|
||||
}
|
||||
|
||||
children.AppendUnstructuredTokens(newline.Tokens())
|
||||
|
||||
// Collect any stragglers, though there shouldn't be any
|
||||
children.AppendUnstructuredTokens(from.Tokens())
|
||||
|
||||
return newNode(attr)
|
||||
}
|
||||
|
||||
func parseBlock(nativeBlock *hclsyntax.Block, from, leadComments, lineComments, newline inputTokens) *node {
|
||||
block := &Block{
|
||||
inTree: newInTree(),
|
||||
labels: newNodeSet(),
|
||||
}
|
||||
children := block.inTree.children
|
||||
|
||||
{
|
||||
cn := newNode(newComments(leadComments.Tokens()))
|
||||
block.leadComments = cn
|
||||
children.AppendNode(cn)
|
||||
}
|
||||
|
||||
before, typeTokens, from := from.Partition(nativeBlock.TypeRange)
|
||||
{
|
||||
children.AppendUnstructuredTokens(before.Tokens())
|
||||
if typeTokens.Len() != 1 {
|
||||
// Should never happen with valid input
|
||||
panic("block type name is not exactly one token")
|
||||
}
|
||||
token := typeTokens.Tokens()[0]
|
||||
in := newNode(newIdentifier(token))
|
||||
block.typeName = in
|
||||
children.AppendNode(in)
|
||||
}
|
||||
|
||||
for _, rng := range nativeBlock.LabelRanges {
|
||||
var labelTokens inputTokens
|
||||
before, labelTokens, from = from.Partition(rng)
|
||||
children.AppendUnstructuredTokens(before.Tokens())
|
||||
tokens := labelTokens.Tokens()
|
||||
var ln *node
|
||||
if len(tokens) == 1 && tokens[0].Type == hclsyntax.TokenIdent {
|
||||
ln = newNode(newIdentifier(tokens[0]))
|
||||
} else {
|
||||
ln = newNode(newQuoted(tokens))
|
||||
}
|
||||
block.labels.Add(ln)
|
||||
children.AppendNode(ln)
|
||||
}
|
||||
|
||||
before, oBrace, from := from.Partition(nativeBlock.OpenBraceRange)
|
||||
children.AppendUnstructuredTokens(before.Tokens())
|
||||
children.AppendUnstructuredTokens(oBrace.Tokens())
|
||||
|
||||
// We go a bit out of order here: we go hunting for the closing brace
|
||||
// so that we have a delimited body, but then we'll deal with the body
|
||||
// before we actually append the closing brace and any straggling tokens
|
||||
// that appear after it.
|
||||
bodyTokens, cBrace, from := from.Partition(nativeBlock.CloseBraceRange)
|
||||
before, body, after := parseBody(nativeBlock.Body, bodyTokens)
|
||||
children.AppendUnstructuredTokens(before.Tokens())
|
||||
block.body = body
|
||||
children.AppendNode(body)
|
||||
children.AppendUnstructuredTokens(after.Tokens())
|
||||
|
||||
children.AppendUnstructuredTokens(cBrace.Tokens())
|
||||
|
||||
// stragglers
|
||||
children.AppendUnstructuredTokens(from.Tokens())
|
||||
if lineComments.Len() > 0 {
|
||||
// blocks don't actually have line comments, so we'll just treat
|
||||
// them as extra stragglers
|
||||
children.AppendUnstructuredTokens(lineComments.Tokens())
|
||||
}
|
||||
children.AppendUnstructuredTokens(newline.Tokens())
|
||||
|
||||
return newNode(block)
|
||||
}
|
||||
|
||||
func parseExpression(nativeExpr hclsyntax.Expression, from inputTokens) *node {
|
||||
expr := newExpression()
|
||||
children := expr.inTree.children
|
||||
|
||||
nativeVars := nativeExpr.Variables()
|
||||
|
||||
for _, nativeTraversal := range nativeVars {
|
||||
before, traversal, after := parseTraversal(nativeTraversal, from)
|
||||
children.AppendUnstructuredTokens(before.Tokens())
|
||||
children.AppendNode(traversal)
|
||||
expr.absTraversals.Add(traversal)
|
||||
from = after
|
||||
}
|
||||
// Attach any stragglers that don't belong to a traversal to the expression
|
||||
// itself. In an expression with no traversals at all, this is just the
|
||||
// entirety of "from".
|
||||
children.AppendUnstructuredTokens(from.Tokens())
|
||||
|
||||
return newNode(expr)
|
||||
}
|
||||
|
||||
func parseTraversal(nativeTraversal hcl.Traversal, from inputTokens) (before inputTokens, n *node, after inputTokens) {
|
||||
traversal := newTraversal()
|
||||
children := traversal.inTree.children
|
||||
before, from, after = from.Partition(nativeTraversal.SourceRange())
|
||||
|
||||
stepAfter := from
|
||||
for _, nativeStep := range nativeTraversal {
|
||||
before, step, after := parseTraversalStep(nativeStep, stepAfter)
|
||||
children.AppendUnstructuredTokens(before.Tokens())
|
||||
children.AppendNode(step)
|
||||
traversal.steps.Add(step)
|
||||
stepAfter = after
|
||||
}
|
||||
|
||||
return before, newNode(traversal), after
|
||||
}
|
||||
|
||||
func parseTraversalStep(nativeStep hcl.Traverser, from inputTokens) (before inputTokens, n *node, after inputTokens) {
|
||||
var children *nodes
|
||||
switch tNativeStep := nativeStep.(type) {
|
||||
|
||||
case hcl.TraverseRoot, hcl.TraverseAttr:
|
||||
step := newTraverseName()
|
||||
children = step.inTree.children
|
||||
before, from, after = from.Partition(nativeStep.SourceRange())
|
||||
inBefore, token, inAfter := from.PartitionTypeSingle(hclsyntax.TokenIdent)
|
||||
name := newIdentifier(token)
|
||||
children.AppendUnstructuredTokens(inBefore.Tokens())
|
||||
step.name = children.Append(name)
|
||||
children.AppendUnstructuredTokens(inAfter.Tokens())
|
||||
return before, newNode(step), after
|
||||
|
||||
case hcl.TraverseIndex:
|
||||
step := newTraverseIndex()
|
||||
children = step.inTree.children
|
||||
before, from, after = from.Partition(nativeStep.SourceRange())
|
||||
|
||||
var inBefore, oBrack, keyTokens, cBrack inputTokens
|
||||
inBefore, oBrack, from = from.PartitionType(hclsyntax.TokenOBrack)
|
||||
children.AppendUnstructuredTokens(inBefore.Tokens())
|
||||
children.AppendUnstructuredTokens(oBrack.Tokens())
|
||||
keyTokens, cBrack, from = from.PartitionType(hclsyntax.TokenCBrack)
|
||||
|
||||
keyVal := tNativeStep.Key
|
||||
switch keyVal.Type() {
|
||||
case cty.String:
|
||||
key := newQuoted(keyTokens.Tokens())
|
||||
step.key = children.Append(key)
|
||||
case cty.Number:
|
||||
valBefore, valToken, valAfter := keyTokens.PartitionTypeSingle(hclsyntax.TokenNumberLit)
|
||||
children.AppendUnstructuredTokens(valBefore.Tokens())
|
||||
key := newNumber(valToken)
|
||||
step.key = children.Append(key)
|
||||
children.AppendUnstructuredTokens(valAfter.Tokens())
|
||||
}
|
||||
|
||||
children.AppendUnstructuredTokens(cBrack.Tokens())
|
||||
children.AppendUnstructuredTokens(from.Tokens())
|
||||
|
||||
return before, newNode(step), after
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported traversal step type %T", nativeStep))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// writerTokens takes a sequence of tokens as produced by the main hclsyntax
|
||||
// package and transforms it into an equivalent sequence of tokens using
|
||||
// this package's own token model.
|
||||
//
|
||||
// The resulting list contains the same number of tokens and uses the same
|
||||
// indices as the input, allowing the two sets of tokens to be correlated
|
||||
// by index.
|
||||
func writerTokens(nativeTokens hclsyntax.Tokens) Tokens {
|
||||
// Ultimately we want a slice of token _pointers_, but since we can
|
||||
// predict how much memory we're going to devote to tokens we'll allocate
|
||||
// it all as a single flat buffer and thus give the GC less work to do.
|
||||
tokBuf := make([]Token, len(nativeTokens))
|
||||
var lastByteOffset int
|
||||
for i, mainToken := range nativeTokens {
|
||||
// Create a copy of the bytes so that we can mutate without
|
||||
// corrupting the original token stream.
|
||||
bytes := make([]byte, len(mainToken.Bytes))
|
||||
copy(bytes, mainToken.Bytes)
|
||||
|
||||
tokBuf[i] = Token{
|
||||
Type: mainToken.Type,
|
||||
Bytes: bytes,
|
||||
|
||||
// We assume here that spaces are always ASCII spaces, since
|
||||
// that's what the scanner also assumes, and thus the number
|
||||
// of bytes skipped is also the number of space characters.
|
||||
SpacesBefore: mainToken.Range.Start.Byte - lastByteOffset,
|
||||
}
|
||||
|
||||
lastByteOffset = mainToken.Range.End.Byte
|
||||
}
|
||||
|
||||
// Now make a slice of pointers into the previous slice.
|
||||
ret := make(Tokens, len(tokBuf))
|
||||
for i := range ret {
|
||||
ret[i] = &tokBuf[i]
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// partitionTokens takes a sequence of tokens and a hcl.Range and returns
|
||||
// two indices within the token sequence that correspond with the range
|
||||
// boundaries, such that the slice operator could be used to produce
|
||||
// three token sequences for before, within, and after respectively:
|
||||
//
|
||||
// start, end := partitionTokens(toks, rng)
|
||||
// before := toks[:start]
|
||||
// within := toks[start:end]
|
||||
// after := toks[end:]
|
||||
//
|
||||
// This works best when the range is aligned with token boundaries (e.g.
|
||||
// because it was produced in terms of the scanner's result) but if that isn't
|
||||
// true then it will make a best effort that may produce strange results at
|
||||
// the boundaries.
|
||||
//
|
||||
// Native hclsyntax tokens are used here, because they contain the necessary
|
||||
// absolute position information. However, since writerTokens produces a
|
||||
// correlatable sequence of writer tokens, the resulting indices can be
|
||||
// used also to index into its result, allowing the partitioning of writer
|
||||
// tokens to be driven by the partitioning of native tokens.
|
||||
//
|
||||
// The tokens are assumed to be in source order and non-overlapping, which
|
||||
// will be true if the token sequence from the scanner is used directly.
|
||||
func partitionTokens(toks hclsyntax.Tokens, rng hcl.Range) (start, end int) {
|
||||
// We us a linear search here because we assume tha in most cases our
|
||||
// target range is close to the beginning of the sequence, and the seqences
|
||||
// are generally small for most reasonable files anyway.
|
||||
for i := 0; ; i++ {
|
||||
if i >= len(toks) {
|
||||
// No tokens for the given range at all!
|
||||
return len(toks), len(toks)
|
||||
}
|
||||
|
||||
if toks[i].Range.Start.Byte >= rng.Start.Byte {
|
||||
start = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for i := start; ; i++ {
|
||||
if i >= len(toks) {
|
||||
// The range "hangs off" the end of the token sequence
|
||||
return start, len(toks)
|
||||
}
|
||||
|
||||
if toks[i].Range.Start.Byte >= rng.End.Byte {
|
||||
end = i // end marker is exclusive
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return start, end
|
||||
}
|
||||
|
||||
// partitionLeadCommentTokens takes a sequence of tokens that is assumed
|
||||
// to immediately precede a construct that can have lead comment tokens,
|
||||
// and returns the index into that sequence where the lead comments begin.
|
||||
//
|
||||
// Lead comments are defined as whole lines containing only comment tokens
|
||||
// with no blank lines between. If no such lines are found, the returned
|
||||
// index will be len(toks).
|
||||
func partitionLeadCommentTokens(toks hclsyntax.Tokens) int {
|
||||
// single-line comments (which is what we're interested in here)
|
||||
// consume their trailing newline, so we can just walk backwards
|
||||
// until we stop seeing comment tokens.
|
||||
for i := len(toks) - 1; i >= 0; i-- {
|
||||
if toks[i].Type != hclsyntax.TokenComment {
|
||||
return i + 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// partitionLineEndTokens takes a sequence of tokens that is assumed
|
||||
// to immediately follow a construct that can have a line comment, and
|
||||
// returns first the index where any line comments end and then second
|
||||
// the index immediately after the trailing newline.
|
||||
//
|
||||
// Line comments are defined as comments that appear immediately after
|
||||
// a construct on the same line where its significant tokens ended.
|
||||
//
|
||||
// Since single-line comment tokens (# and //) include the newline that
|
||||
// terminates them, in the presence of these the two returned indices
|
||||
// will be the same since the comment itself serves as the line end.
|
||||
func partitionLineEndTokens(toks hclsyntax.Tokens) (afterComment, afterNewline int) {
|
||||
for i := 0; i < len(toks); i++ {
|
||||
tok := toks[i]
|
||||
if tok.Type != hclsyntax.TokenComment {
|
||||
switch tok.Type {
|
||||
case hclsyntax.TokenNewline:
|
||||
return i, i + 1
|
||||
case hclsyntax.TokenEOF:
|
||||
// Although this is valid, we mustn't include the EOF
|
||||
// itself as our "newline" or else strange things will
|
||||
// happen when we try to append new items.
|
||||
return i, i
|
||||
default:
|
||||
// If we have well-formed input here then nothing else should be
|
||||
// possible. This path should never happen, because we only try
|
||||
// to extract tokens from the sequence if the parser succeeded,
|
||||
// and it should catch this problem itself.
|
||||
panic("malformed line trailers: expected only comments and newlines")
|
||||
}
|
||||
}
|
||||
|
||||
if len(tok.Bytes) > 0 && tok.Bytes[len(tok.Bytes)-1] == '\n' {
|
||||
// Newline at the end of a single-line comment serves both as
|
||||
// the end of comments *and* the end of the line.
|
||||
return i + 1, i + 1
|
||||
}
|
||||
}
|
||||
return len(toks), len(toks)
|
||||
}
|
||||
|
||||
// lexConfig uses the hclsyntax scanner to get a token stream and then
|
||||
// rewrites it into this package's token model.
|
||||
//
|
||||
// Any errors produced during scanning are ignored, so the results of this
|
||||
// function should be used with care.
|
||||
func lexConfig(src []byte) Tokens {
|
||||
mainTokens, _ := hclsyntax.LexConfig(src, "", hcl.Pos{Byte: 0, Line: 1, Column: 1})
|
||||
return writerTokens(mainTokens)
|
||||
}
|
44
vendor/github.com/hashicorp/hcl/v2/hclwrite/public.go
generated
vendored
Normal file
44
vendor/github.com/hashicorp/hcl/v2/hclwrite/public.go
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
package hclwrite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// NewFile creates a new file object that is empty and ready to have constructs
|
||||
// added t it.
|
||||
func NewFile() *File {
|
||||
body := &Body{
|
||||
inTree: newInTree(),
|
||||
items: newNodeSet(),
|
||||
}
|
||||
file := &File{
|
||||
inTree: newInTree(),
|
||||
}
|
||||
file.body = file.inTree.children.Append(body)
|
||||
return file
|
||||
}
|
||||
|
||||
// ParseConfig interprets the given source bytes into a *hclwrite.File. The
|
||||
// resulting AST can be used to perform surgical edits on the source code
|
||||
// before turning it back into bytes again.
|
||||
func ParseConfig(src []byte, filename string, start hcl.Pos) (*File, hcl.Diagnostics) {
|
||||
return parse(src, filename, start)
|
||||
}
|
||||
|
||||
// Format takes source code and performs simple whitespace changes to transform
|
||||
// it to a canonical layout style.
|
||||
//
|
||||
// Format skips constructing an AST and works directly with tokens, so it
|
||||
// is less expensive than formatting via the AST for situations where no other
|
||||
// changes will be made. It also ignores syntax errors and can thus be applied
|
||||
// to partial source code, although the result in that case may not be
|
||||
// desirable.
|
||||
func Format(src []byte) []byte {
|
||||
tokens := lexConfig(src)
|
||||
format(tokens)
|
||||
buf := &bytes.Buffer{}
|
||||
tokens.WriteTo(buf)
|
||||
return buf.Bytes()
|
||||
}
|
122
vendor/github.com/hashicorp/hcl/v2/hclwrite/tokens.go
generated
vendored
Normal file
122
vendor/github.com/hashicorp/hcl/v2/hclwrite/tokens.go
generated
vendored
Normal file
@ -0,0 +1,122 @@
|
||||
package hclwrite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/apparentlymart/go-textseg/v12/textseg"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
)
|
||||
|
||||
// Token is a single sequence of bytes annotated with a type. It is similar
|
||||
// in purpose to hclsyntax.Token, but discards the source position information
|
||||
// since that is not useful in code generation.
|
||||
type Token struct {
|
||||
Type hclsyntax.TokenType
|
||||
Bytes []byte
|
||||
|
||||
// We record the number of spaces before each token so that we can
|
||||
// reproduce the exact layout of the original file when we're making
|
||||
// surgical changes in-place. When _new_ code is created it will always
|
||||
// be in the canonical style, but we preserve layout of existing code.
|
||||
SpacesBefore int
|
||||
}
|
||||
|
||||
// asHCLSyntax returns the receiver expressed as an incomplete hclsyntax.Token.
|
||||
// A complete token is not possible since we don't have source location
|
||||
// information here, and so this method is unexported so we can be sure it will
|
||||
// only be used for internal purposes where we know the range isn't important.
|
||||
//
|
||||
// This is primarily intended to allow us to re-use certain functionality from
|
||||
// hclsyntax rather than re-implementing it against our own token type here.
|
||||
func (t *Token) asHCLSyntax() hclsyntax.Token {
|
||||
return hclsyntax.Token{
|
||||
Type: t.Type,
|
||||
Bytes: t.Bytes,
|
||||
Range: hcl.Range{
|
||||
Filename: "<invalid>",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Tokens is a flat list of tokens.
|
||||
type Tokens []*Token
|
||||
|
||||
func (ts Tokens) Bytes() []byte {
|
||||
buf := &bytes.Buffer{}
|
||||
ts.WriteTo(buf)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (ts Tokens) testValue() string {
|
||||
return string(ts.Bytes())
|
||||
}
|
||||
|
||||
// Columns returns the number of columns (grapheme clusters) the token sequence
|
||||
// occupies. The result is not meaningful if there are newline or single-line
|
||||
// comment tokens in the sequence.
|
||||
func (ts Tokens) Columns() int {
|
||||
ret := 0
|
||||
for _, token := range ts {
|
||||
ret += token.SpacesBefore // spaces are always worth one column each
|
||||
ct, _ := textseg.TokenCount(token.Bytes, textseg.ScanGraphemeClusters)
|
||||
ret += ct
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// WriteTo takes an io.Writer and writes the bytes for each token to it,
|
||||
// along with the spacing that separates each token. In other words, this
|
||||
// allows serializing the tokens to a file or other such byte stream.
|
||||
func (ts Tokens) WriteTo(wr io.Writer) (int64, error) {
|
||||
// We know we're going to be writing a lot of small chunks of repeated
|
||||
// space characters, so we'll prepare a buffer of these that we can
|
||||
// easily pass to wr.Write without any further allocation.
|
||||
spaces := make([]byte, 40)
|
||||
for i := range spaces {
|
||||
spaces[i] = ' '
|
||||
}
|
||||
|
||||
var n int64
|
||||
var err error
|
||||
for _, token := range ts {
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
|
||||
for spacesBefore := token.SpacesBefore; spacesBefore > 0; spacesBefore -= len(spaces) {
|
||||
thisChunk := spacesBefore
|
||||
if thisChunk > len(spaces) {
|
||||
thisChunk = len(spaces)
|
||||
}
|
||||
var thisN int
|
||||
thisN, err = wr.Write(spaces[:thisChunk])
|
||||
n += int64(thisN)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
|
||||
var thisN int
|
||||
thisN, err = wr.Write(token.Bytes)
|
||||
n += int64(thisN)
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (ts Tokens) walkChildNodes(w internalWalkFunc) {
|
||||
// Unstructured tokens have no child nodes
|
||||
}
|
||||
|
||||
func (ts Tokens) BuildTokens(to Tokens) Tokens {
|
||||
return append(to, ts...)
|
||||
}
|
||||
|
||||
func newIdentToken(name string) *Token {
|
||||
return &Token{
|
||||
Type: hclsyntax.TokenIdent,
|
||||
Bytes: []byte(name),
|
||||
}
|
||||
}
|
121
vendor/github.com/hashicorp/hcl/v2/json/ast.go
generated
vendored
Normal file
121
vendor/github.com/hashicorp/hcl/v2/json/ast.go
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
type node interface {
|
||||
Range() hcl.Range
|
||||
StartRange() hcl.Range
|
||||
}
|
||||
|
||||
type objectVal struct {
|
||||
Attrs []*objectAttr
|
||||
SrcRange hcl.Range // range of the entire object, brace-to-brace
|
||||
OpenRange hcl.Range // range of the opening brace
|
||||
CloseRange hcl.Range // range of the closing brace
|
||||
}
|
||||
|
||||
func (n *objectVal) Range() hcl.Range {
|
||||
return n.SrcRange
|
||||
}
|
||||
|
||||
func (n *objectVal) StartRange() hcl.Range {
|
||||
return n.OpenRange
|
||||
}
|
||||
|
||||
type objectAttr struct {
|
||||
Name string
|
||||
Value node
|
||||
NameRange hcl.Range // range of the name string
|
||||
}
|
||||
|
||||
func (n *objectAttr) Range() hcl.Range {
|
||||
return n.NameRange
|
||||
}
|
||||
|
||||
func (n *objectAttr) StartRange() hcl.Range {
|
||||
return n.NameRange
|
||||
}
|
||||
|
||||
type arrayVal struct {
|
||||
Values []node
|
||||
SrcRange hcl.Range // range of the entire object, bracket-to-bracket
|
||||
OpenRange hcl.Range // range of the opening bracket
|
||||
}
|
||||
|
||||
func (n *arrayVal) Range() hcl.Range {
|
||||
return n.SrcRange
|
||||
}
|
||||
|
||||
func (n *arrayVal) StartRange() hcl.Range {
|
||||
return n.OpenRange
|
||||
}
|
||||
|
||||
type booleanVal struct {
|
||||
Value bool
|
||||
SrcRange hcl.Range
|
||||
}
|
||||
|
||||
func (n *booleanVal) Range() hcl.Range {
|
||||
return n.SrcRange
|
||||
}
|
||||
|
||||
func (n *booleanVal) StartRange() hcl.Range {
|
||||
return n.SrcRange
|
||||
}
|
||||
|
||||
type numberVal struct {
|
||||
Value *big.Float
|
||||
SrcRange hcl.Range
|
||||
}
|
||||
|
||||
func (n *numberVal) Range() hcl.Range {
|
||||
return n.SrcRange
|
||||
}
|
||||
|
||||
func (n *numberVal) StartRange() hcl.Range {
|
||||
return n.SrcRange
|
||||
}
|
||||
|
||||
type stringVal struct {
|
||||
Value string
|
||||
SrcRange hcl.Range
|
||||
}
|
||||
|
||||
func (n *stringVal) Range() hcl.Range {
|
||||
return n.SrcRange
|
||||
}
|
||||
|
||||
func (n *stringVal) StartRange() hcl.Range {
|
||||
return n.SrcRange
|
||||
}
|
||||
|
||||
type nullVal struct {
|
||||
SrcRange hcl.Range
|
||||
}
|
||||
|
||||
func (n *nullVal) Range() hcl.Range {
|
||||
return n.SrcRange
|
||||
}
|
||||
|
||||
func (n *nullVal) StartRange() hcl.Range {
|
||||
return n.SrcRange
|
||||
}
|
||||
|
||||
// invalidVal is used as a placeholder where a value is needed for a valid
|
||||
// parse tree but the input was invalid enough to prevent one from being
|
||||
// created.
|
||||
type invalidVal struct {
|
||||
SrcRange hcl.Range
|
||||
}
|
||||
|
||||
func (n invalidVal) Range() hcl.Range {
|
||||
return n.SrcRange
|
||||
}
|
||||
|
||||
func (n invalidVal) StartRange() hcl.Range {
|
||||
return n.SrcRange
|
||||
}
|
33
vendor/github.com/hashicorp/hcl/v2/json/didyoumean.go
generated
vendored
Normal file
33
vendor/github.com/hashicorp/hcl/v2/json/didyoumean.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"github.com/agext/levenshtein"
|
||||
)
|
||||
|
||||
var keywords = []string{"false", "true", "null"}
|
||||
|
||||
// keywordSuggestion tries to find a valid JSON keyword that is close to the
|
||||
// given string and returns it if found. If no keyword is close enough, returns
|
||||
// the empty string.
|
||||
func keywordSuggestion(given string) string {
|
||||
return nameSuggestion(given, keywords)
|
||||
}
|
||||
|
||||
// nameSuggestion tries to find a name from the given slice of suggested names
|
||||
// that is close to the given name and returns it if found. If no suggestion
|
||||
// is close enough, returns the empty string.
|
||||
//
|
||||
// The suggestions are tried in order, so earlier suggestions take precedence
|
||||
// if the given string is similar to two or more suggestions.
|
||||
//
|
||||
// This function is intended to be used with a relatively-small number of
|
||||
// suggestions. It's not optimized for hundreds or thousands of them.
|
||||
func nameSuggestion(given string, suggestions []string) string {
|
||||
for _, suggestion := range suggestions {
|
||||
dist := levenshtein.Distance(given, suggestion, nil)
|
||||
if dist < 3 { // threshold determined experimentally
|
||||
return suggestion
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
12
vendor/github.com/hashicorp/hcl/v2/json/doc.go
generated
vendored
Normal file
12
vendor/github.com/hashicorp/hcl/v2/json/doc.go
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// Package json is the JSON parser for HCL. It parses JSON files and returns
|
||||
// implementations of the core HCL structural interfaces in terms of the
|
||||
// JSON data inside.
|
||||
//
|
||||
// This is not a generic JSON parser. Instead, it deals with the mapping from
|
||||
// the JSON information model to the HCL information model, using a number
|
||||
// of hard-coded structural conventions.
|
||||
//
|
||||
// In most cases applications will not import this package directly, but will
|
||||
// instead access its functionality indirectly through functions in the main
|
||||
// "hcl" package and in the "hclparse" package.
|
||||
package json
|
70
vendor/github.com/hashicorp/hcl/v2/json/navigation.go
generated
vendored
Normal file
70
vendor/github.com/hashicorp/hcl/v2/json/navigation.go
generated
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type navigation struct {
|
||||
root node
|
||||
}
|
||||
|
||||
// Implementation of hcled.ContextString
|
||||
func (n navigation) ContextString(offset int) string {
|
||||
steps := navigationStepsRev(n.root, offset)
|
||||
if steps == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// We built our slice backwards, so we'll reverse it in-place now.
|
||||
half := len(steps) / 2 // integer division
|
||||
for i := 0; i < half; i++ {
|
||||
steps[i], steps[len(steps)-1-i] = steps[len(steps)-1-i], steps[i]
|
||||
}
|
||||
|
||||
ret := strings.Join(steps, "")
|
||||
if len(ret) > 0 && ret[0] == '.' {
|
||||
ret = ret[1:]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func navigationStepsRev(v node, offset int) []string {
|
||||
switch tv := v.(type) {
|
||||
case *objectVal:
|
||||
// Do any of our properties have an object that contains the target
|
||||
// offset?
|
||||
for _, attr := range tv.Attrs {
|
||||
k := attr.Name
|
||||
av := attr.Value
|
||||
|
||||
switch av.(type) {
|
||||
case *objectVal, *arrayVal:
|
||||
// okay
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if av.Range().ContainsOffset(offset) {
|
||||
return append(navigationStepsRev(av, offset), "."+k)
|
||||
}
|
||||
}
|
||||
case *arrayVal:
|
||||
// Do any of our elements contain the target offset?
|
||||
for i, elem := range tv.Values {
|
||||
|
||||
switch elem.(type) {
|
||||
case *objectVal, *arrayVal:
|
||||
// okay
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
if elem.Range().ContainsOffset(offset) {
|
||||
return append(navigationStepsRev(elem, offset), fmt.Sprintf("[%d]", i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
496
vendor/github.com/hashicorp/hcl/v2/json/parser.go
generated
vendored
Normal file
496
vendor/github.com/hashicorp/hcl/v2/json/parser.go
generated
vendored
Normal file
@ -0,0 +1,496 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func parseFileContent(buf []byte, filename string) (node, hcl.Diagnostics) {
|
||||
tokens := scan(buf, pos{
|
||||
Filename: filename,
|
||||
Pos: hcl.Pos{
|
||||
Byte: 0,
|
||||
Line: 1,
|
||||
Column: 1,
|
||||
},
|
||||
})
|
||||
p := newPeeker(tokens)
|
||||
node, diags := parseValue(p)
|
||||
if len(diags) == 0 && p.Peek().Type != tokenEOF {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Extraneous data after value",
|
||||
Detail: "Extra characters appear after the JSON value.",
|
||||
Subject: p.Peek().Range.Ptr(),
|
||||
})
|
||||
}
|
||||
return node, diags
|
||||
}
|
||||
|
||||
func parseValue(p *peeker) (node, hcl.Diagnostics) {
|
||||
tok := p.Peek()
|
||||
|
||||
wrapInvalid := func(n node, diags hcl.Diagnostics) (node, hcl.Diagnostics) {
|
||||
if n != nil {
|
||||
return n, diags
|
||||
}
|
||||
return invalidVal{tok.Range}, diags
|
||||
}
|
||||
|
||||
switch tok.Type {
|
||||
case tokenBraceO:
|
||||
return wrapInvalid(parseObject(p))
|
||||
case tokenBrackO:
|
||||
return wrapInvalid(parseArray(p))
|
||||
case tokenNumber:
|
||||
return wrapInvalid(parseNumber(p))
|
||||
case tokenString:
|
||||
return wrapInvalid(parseString(p))
|
||||
case tokenKeyword:
|
||||
return wrapInvalid(parseKeyword(p))
|
||||
case tokenBraceC:
|
||||
return wrapInvalid(nil, hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing JSON value",
|
||||
Detail: "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.",
|
||||
Subject: &tok.Range,
|
||||
},
|
||||
})
|
||||
case tokenBrackC:
|
||||
return wrapInvalid(nil, hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing array element value",
|
||||
Detail: "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.",
|
||||
Subject: &tok.Range,
|
||||
},
|
||||
})
|
||||
case tokenEOF:
|
||||
return wrapInvalid(nil, hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing value",
|
||||
Detail: "The JSON data ends prematurely.",
|
||||
Subject: &tok.Range,
|
||||
},
|
||||
})
|
||||
default:
|
||||
return wrapInvalid(nil, hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid start of value",
|
||||
Detail: "A JSON value must start with a brace, a bracket, a number, a string, or a keyword.",
|
||||
Subject: &tok.Range,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func tokenCanStartValue(tok token) bool {
|
||||
switch tok.Type {
|
||||
case tokenBraceO, tokenBrackO, tokenNumber, tokenString, tokenKeyword:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func parseObject(p *peeker) (node, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
open := p.Read()
|
||||
attrs := []*objectAttr{}
|
||||
|
||||
// recover is used to shift the peeker to what seems to be the end of
|
||||
// our object, so that when we encounter an error we leave the peeker
|
||||
// at a reasonable point in the token stream to continue parsing.
|
||||
recover := func(tok token) {
|
||||
open := 1
|
||||
for {
|
||||
switch tok.Type {
|
||||
case tokenBraceO:
|
||||
open++
|
||||
case tokenBraceC:
|
||||
open--
|
||||
if open <= 1 {
|
||||
return
|
||||
}
|
||||
case tokenEOF:
|
||||
// Ran out of source before we were able to recover,
|
||||
// so we'll bail here and let the caller deal with it.
|
||||
return
|
||||
}
|
||||
tok = p.Read()
|
||||
}
|
||||
}
|
||||
|
||||
Token:
|
||||
for {
|
||||
if p.Peek().Type == tokenBraceC {
|
||||
break Token
|
||||
}
|
||||
|
||||
keyNode, keyDiags := parseValue(p)
|
||||
diags = diags.Extend(keyDiags)
|
||||
if keyNode == nil {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
keyStrNode, ok := keyNode.(*stringVal)
|
||||
if !ok {
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid object property name",
|
||||
Detail: "A JSON object property name must be a string",
|
||||
Subject: keyNode.StartRange().Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
key := keyStrNode.Value
|
||||
|
||||
colon := p.Read()
|
||||
if colon.Type != tokenColon {
|
||||
recover(colon)
|
||||
|
||||
if colon.Type == tokenBraceC || colon.Type == tokenComma {
|
||||
// Catch common mistake of using braces instead of brackets
|
||||
// for an object.
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing object value",
|
||||
Detail: "A JSON object attribute must have a value, introduced by a colon.",
|
||||
Subject: &colon.Range,
|
||||
})
|
||||
}
|
||||
|
||||
if colon.Type == tokenEquals {
|
||||
// Possible confusion with native HCL syntax.
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing property value colon",
|
||||
Detail: "JSON uses a colon as its name/value delimiter, not an equals sign.",
|
||||
Subject: &colon.Range,
|
||||
})
|
||||
}
|
||||
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing property value colon",
|
||||
Detail: "A colon must appear between an object property's name and its value.",
|
||||
Subject: &colon.Range,
|
||||
})
|
||||
}
|
||||
|
||||
valNode, valDiags := parseValue(p)
|
||||
diags = diags.Extend(valDiags)
|
||||
if valNode == nil {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
attrs = append(attrs, &objectAttr{
|
||||
Name: key,
|
||||
Value: valNode,
|
||||
NameRange: keyStrNode.SrcRange,
|
||||
})
|
||||
|
||||
switch p.Peek().Type {
|
||||
case tokenComma:
|
||||
comma := p.Read()
|
||||
if p.Peek().Type == tokenBraceC {
|
||||
// Special error message for this common mistake
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Trailing comma in object",
|
||||
Detail: "JSON does not permit a trailing comma after the final property in an object.",
|
||||
Subject: &comma.Range,
|
||||
})
|
||||
}
|
||||
continue Token
|
||||
case tokenEOF:
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unclosed object",
|
||||
Detail: "No closing brace was found for this JSON object.",
|
||||
Subject: &open.Range,
|
||||
})
|
||||
case tokenBrackC:
|
||||
// Consume the bracket anyway, so that we don't return with the peeker
|
||||
// at a strange place.
|
||||
p.Read()
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Mismatched braces",
|
||||
Detail: "A JSON object must be closed with a brace, not a bracket.",
|
||||
Subject: p.Peek().Range.Ptr(),
|
||||
})
|
||||
case tokenBraceC:
|
||||
break Token
|
||||
default:
|
||||
recover(p.Read())
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing attribute seperator comma",
|
||||
Detail: "A comma must appear between each property definition in an object.",
|
||||
Subject: p.Peek().Range.Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
close := p.Read()
|
||||
return &objectVal{
|
||||
Attrs: attrs,
|
||||
SrcRange: hcl.RangeBetween(open.Range, close.Range),
|
||||
OpenRange: open.Range,
|
||||
CloseRange: close.Range,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func parseArray(p *peeker) (node, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
open := p.Read()
|
||||
vals := []node{}
|
||||
|
||||
// recover is used to shift the peeker to what seems to be the end of
|
||||
// our array, so that when we encounter an error we leave the peeker
|
||||
// at a reasonable point in the token stream to continue parsing.
|
||||
recover := func(tok token) {
|
||||
open := 1
|
||||
for {
|
||||
switch tok.Type {
|
||||
case tokenBrackO:
|
||||
open++
|
||||
case tokenBrackC:
|
||||
open--
|
||||
if open <= 1 {
|
||||
return
|
||||
}
|
||||
case tokenEOF:
|
||||
// Ran out of source before we were able to recover,
|
||||
// so we'll bail here and let the caller deal with it.
|
||||
return
|
||||
}
|
||||
tok = p.Read()
|
||||
}
|
||||
}
|
||||
|
||||
Token:
|
||||
for {
|
||||
if p.Peek().Type == tokenBrackC {
|
||||
break Token
|
||||
}
|
||||
|
||||
valNode, valDiags := parseValue(p)
|
||||
diags = diags.Extend(valDiags)
|
||||
if valNode == nil {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
vals = append(vals, valNode)
|
||||
|
||||
switch p.Peek().Type {
|
||||
case tokenComma:
|
||||
comma := p.Read()
|
||||
if p.Peek().Type == tokenBrackC {
|
||||
// Special error message for this common mistake
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Trailing comma in array",
|
||||
Detail: "JSON does not permit a trailing comma after the final value in an array.",
|
||||
Subject: &comma.Range,
|
||||
})
|
||||
}
|
||||
continue Token
|
||||
case tokenColon:
|
||||
recover(p.Read())
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid array value",
|
||||
Detail: "A colon is not used to introduce values in a JSON array.",
|
||||
Subject: p.Peek().Range.Ptr(),
|
||||
})
|
||||
case tokenEOF:
|
||||
recover(p.Read())
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unclosed object",
|
||||
Detail: "No closing bracket was found for this JSON array.",
|
||||
Subject: &open.Range,
|
||||
})
|
||||
case tokenBraceC:
|
||||
recover(p.Read())
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Mismatched brackets",
|
||||
Detail: "A JSON array must be closed with a bracket, not a brace.",
|
||||
Subject: p.Peek().Range.Ptr(),
|
||||
})
|
||||
case tokenBrackC:
|
||||
break Token
|
||||
default:
|
||||
recover(p.Read())
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing attribute seperator comma",
|
||||
Detail: "A comma must appear between each value in an array.",
|
||||
Subject: p.Peek().Range.Ptr(),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
close := p.Read()
|
||||
return &arrayVal{
|
||||
Values: vals,
|
||||
SrcRange: hcl.RangeBetween(open.Range, close.Range),
|
||||
OpenRange: open.Range,
|
||||
}, diags
|
||||
}
|
||||
|
||||
func parseNumber(p *peeker) (node, hcl.Diagnostics) {
|
||||
tok := p.Read()
|
||||
|
||||
// Use encoding/json to validate the number syntax.
|
||||
// TODO: Do this more directly to produce better diagnostics.
|
||||
var num json.Number
|
||||
err := json.Unmarshal(tok.Bytes, &num)
|
||||
if err != nil {
|
||||
return nil, hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid JSON number",
|
||||
Detail: fmt.Sprintf("There is a syntax error in the given JSON number."),
|
||||
Subject: &tok.Range,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// We want to guarantee that we parse numbers the same way as cty (and thus
|
||||
// native syntax HCL) would here, so we'll use the cty parser even though
|
||||
// in most other cases we don't actually introduce cty concepts until
|
||||
// decoding time. We'll unwrap the parsed float immediately afterwards, so
|
||||
// the cty value is just a temporary helper.
|
||||
nv, err := cty.ParseNumberVal(string(num))
|
||||
if err != nil {
|
||||
// Should never happen if above passed, since JSON numbers are a subset
|
||||
// of what cty can parse...
|
||||
return nil, hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid JSON number",
|
||||
Detail: fmt.Sprintf("There is a syntax error in the given JSON number."),
|
||||
Subject: &tok.Range,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &numberVal{
|
||||
Value: nv.AsBigFloat(),
|
||||
SrcRange: tok.Range,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseString(p *peeker) (node, hcl.Diagnostics) {
|
||||
tok := p.Read()
|
||||
var str string
|
||||
err := json.Unmarshal(tok.Bytes, &str)
|
||||
|
||||
if err != nil {
|
||||
var errRange hcl.Range
|
||||
if serr, ok := err.(*json.SyntaxError); ok {
|
||||
errOfs := serr.Offset
|
||||
errPos := tok.Range.Start
|
||||
errPos.Byte += int(errOfs)
|
||||
|
||||
// TODO: Use the byte offset to properly count unicode
|
||||
// characters for the column, and mark the whole of the
|
||||
// character that was wrong as part of our range.
|
||||
errPos.Column += int(errOfs)
|
||||
|
||||
errEndPos := errPos
|
||||
errEndPos.Byte++
|
||||
errEndPos.Column++
|
||||
|
||||
errRange = hcl.Range{
|
||||
Filename: tok.Range.Filename,
|
||||
Start: errPos,
|
||||
End: errEndPos,
|
||||
}
|
||||
} else {
|
||||
errRange = tok.Range
|
||||
}
|
||||
|
||||
var contextRange *hcl.Range
|
||||
if errRange != tok.Range {
|
||||
contextRange = &tok.Range
|
||||
}
|
||||
|
||||
// FIXME: Eventually we should parse strings directly here so
|
||||
// we can produce a more useful error message in the face fo things
|
||||
// such as invalid escapes, etc.
|
||||
return nil, hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid JSON string",
|
||||
Detail: fmt.Sprintf("There is a syntax error in the given JSON string."),
|
||||
Subject: &errRange,
|
||||
Context: contextRange,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &stringVal{
|
||||
Value: str,
|
||||
SrcRange: tok.Range,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseKeyword(p *peeker) (node, hcl.Diagnostics) {
|
||||
tok := p.Read()
|
||||
s := string(tok.Bytes)
|
||||
|
||||
switch s {
|
||||
case "true":
|
||||
return &booleanVal{
|
||||
Value: true,
|
||||
SrcRange: tok.Range,
|
||||
}, nil
|
||||
case "false":
|
||||
return &booleanVal{
|
||||
Value: false,
|
||||
SrcRange: tok.Range,
|
||||
}, nil
|
||||
case "null":
|
||||
return &nullVal{
|
||||
SrcRange: tok.Range,
|
||||
}, nil
|
||||
case "undefined", "NaN", "Infinity":
|
||||
return nil, hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid JSON keyword",
|
||||
Detail: fmt.Sprintf("The JavaScript identifier %q cannot be used in JSON.", s),
|
||||
Subject: &tok.Range,
|
||||
},
|
||||
}
|
||||
default:
|
||||
var dym string
|
||||
if suggest := keywordSuggestion(s); suggest != "" {
|
||||
dym = fmt.Sprintf(" Did you mean %q?", suggest)
|
||||
}
|
||||
|
||||
return nil, hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid JSON keyword",
|
||||
Detail: fmt.Sprintf("%q is not a valid JSON keyword.%s", s, dym),
|
||||
Subject: &tok.Range,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
25
vendor/github.com/hashicorp/hcl/v2/json/peeker.go
generated
vendored
Normal file
25
vendor/github.com/hashicorp/hcl/v2/json/peeker.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
package json
|
||||
|
||||
type peeker struct {
|
||||
tokens []token
|
||||
pos int
|
||||
}
|
||||
|
||||
func newPeeker(tokens []token) *peeker {
|
||||
return &peeker{
|
||||
tokens: tokens,
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *peeker) Peek() token {
|
||||
return p.tokens[p.pos]
|
||||
}
|
||||
|
||||
func (p *peeker) Read() token {
|
||||
ret := p.tokens[p.pos]
|
||||
if ret.Type != tokenEOF {
|
||||
p.pos++
|
||||
}
|
||||
return ret
|
||||
}
|
94
vendor/github.com/hashicorp/hcl/v2/json/public.go
generated
vendored
Normal file
94
vendor/github.com/hashicorp/hcl/v2/json/public.go
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// Parse attempts to parse the given buffer as JSON and, if successful, returns
|
||||
// a hcl.File for the HCL configuration represented by it.
|
||||
//
|
||||
// This is not a generic JSON parser. Instead, it deals only with the profile
|
||||
// of JSON used to express HCL configuration.
|
||||
//
|
||||
// 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) (*hcl.File, hcl.Diagnostics) {
|
||||
rootNode, diags := parseFileContent(src, filename)
|
||||
|
||||
switch rootNode.(type) {
|
||||
case *objectVal, *arrayVal:
|
||||
// okay
|
||||
default:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Root value must be object",
|
||||
Detail: "The root value in a JSON-based configuration must be either a JSON object or a JSON array of objects.",
|
||||
Subject: rootNode.StartRange().Ptr(),
|
||||
})
|
||||
|
||||
// Since we've already produced an error message for this being
|
||||
// invalid, we'll return an empty placeholder here so that trying to
|
||||
// extract content from our root body won't produce a redundant
|
||||
// error saying the same thing again in more general terms.
|
||||
fakePos := hcl.Pos{
|
||||
Byte: 0,
|
||||
Line: 1,
|
||||
Column: 1,
|
||||
}
|
||||
fakeRange := hcl.Range{
|
||||
Filename: filename,
|
||||
Start: fakePos,
|
||||
End: fakePos,
|
||||
}
|
||||
rootNode = &objectVal{
|
||||
Attrs: []*objectAttr{},
|
||||
SrcRange: fakeRange,
|
||||
OpenRange: fakeRange,
|
||||
}
|
||||
}
|
||||
|
||||
file := &hcl.File{
|
||||
Body: &body{
|
||||
val: rootNode,
|
||||
},
|
||||
Bytes: src,
|
||||
Nav: navigation{rootNode},
|
||||
}
|
||||
return file, diags
|
||||
}
|
||||
|
||||
// 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) (*hcl.File, hcl.Diagnostics) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.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, hcl.Diagnostics{
|
||||
{
|
||||
Severity: hcl.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)
|
||||
}
|
306
vendor/github.com/hashicorp/hcl/v2/json/scanner.go
generated
vendored
Normal file
306
vendor/github.com/hashicorp/hcl/v2/json/scanner.go
generated
vendored
Normal file
@ -0,0 +1,306 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/apparentlymart/go-textseg/v12/textseg"
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
//go:generate stringer -type tokenType scanner.go
|
||||
type tokenType rune
|
||||
|
||||
const (
|
||||
tokenBraceO tokenType = '{'
|
||||
tokenBraceC tokenType = '}'
|
||||
tokenBrackO tokenType = '['
|
||||
tokenBrackC tokenType = ']'
|
||||
tokenComma tokenType = ','
|
||||
tokenColon tokenType = ':'
|
||||
tokenKeyword tokenType = 'K'
|
||||
tokenString tokenType = 'S'
|
||||
tokenNumber tokenType = 'N'
|
||||
tokenEOF tokenType = '␄'
|
||||
tokenInvalid tokenType = 0
|
||||
tokenEquals tokenType = '=' // used only for reminding the user of JSON syntax
|
||||
)
|
||||
|
||||
type token struct {
|
||||
Type tokenType
|
||||
Bytes []byte
|
||||
Range hcl.Range
|
||||
}
|
||||
|
||||
// scan returns the primary tokens for the given JSON buffer in sequence.
|
||||
//
|
||||
// The responsibility of this pass is to just mark the slices of the buffer
|
||||
// as being of various types. It is lax in how it interprets the multi-byte
|
||||
// token types keyword, string and number, preferring to capture erroneous
|
||||
// extra bytes that we presume the user intended to be part of the token
|
||||
// so that we can generate more helpful diagnostics in the parser.
|
||||
func scan(buf []byte, start pos) []token {
|
||||
var tokens []token
|
||||
p := start
|
||||
for {
|
||||
if len(buf) == 0 {
|
||||
tokens = append(tokens, token{
|
||||
Type: tokenEOF,
|
||||
Bytes: nil,
|
||||
Range: posRange(p, p),
|
||||
})
|
||||
return tokens
|
||||
}
|
||||
|
||||
buf, p = skipWhitespace(buf, p)
|
||||
|
||||
if len(buf) == 0 {
|
||||
tokens = append(tokens, token{
|
||||
Type: tokenEOF,
|
||||
Bytes: nil,
|
||||
Range: posRange(p, p),
|
||||
})
|
||||
return tokens
|
||||
}
|
||||
|
||||
start = p
|
||||
|
||||
first := buf[0]
|
||||
switch {
|
||||
case first == '{' || first == '}' || first == '[' || first == ']' || first == ',' || first == ':' || first == '=':
|
||||
p.Pos.Column++
|
||||
p.Pos.Byte++
|
||||
tokens = append(tokens, token{
|
||||
Type: tokenType(first),
|
||||
Bytes: buf[0:1],
|
||||
Range: posRange(start, p),
|
||||
})
|
||||
buf = buf[1:]
|
||||
case first == '"':
|
||||
var tokBuf []byte
|
||||
tokBuf, buf, p = scanString(buf, p)
|
||||
tokens = append(tokens, token{
|
||||
Type: tokenString,
|
||||
Bytes: tokBuf,
|
||||
Range: posRange(start, p),
|
||||
})
|
||||
case byteCanStartNumber(first):
|
||||
var tokBuf []byte
|
||||
tokBuf, buf, p = scanNumber(buf, p)
|
||||
tokens = append(tokens, token{
|
||||
Type: tokenNumber,
|
||||
Bytes: tokBuf,
|
||||
Range: posRange(start, p),
|
||||
})
|
||||
case byteCanStartKeyword(first):
|
||||
var tokBuf []byte
|
||||
tokBuf, buf, p = scanKeyword(buf, p)
|
||||
tokens = append(tokens, token{
|
||||
Type: tokenKeyword,
|
||||
Bytes: tokBuf,
|
||||
Range: posRange(start, p),
|
||||
})
|
||||
default:
|
||||
tokens = append(tokens, token{
|
||||
Type: tokenInvalid,
|
||||
Bytes: buf[:1],
|
||||
Range: start.Range(1, 1),
|
||||
})
|
||||
// If we've encountered an invalid then we might as well stop
|
||||
// scanning since the parser won't proceed beyond this point.
|
||||
// We insert a synthetic EOF marker here to match the expectations
|
||||
// of consumers of this data structure.
|
||||
p.Pos.Column++
|
||||
p.Pos.Byte++
|
||||
tokens = append(tokens, token{
|
||||
Type: tokenEOF,
|
||||
Bytes: nil,
|
||||
Range: posRange(p, p),
|
||||
})
|
||||
return tokens
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func byteCanStartNumber(b byte) bool {
|
||||
switch b {
|
||||
// We are slightly more tolerant than JSON requires here since we
|
||||
// expect the parser will make a stricter interpretation of the
|
||||
// number bytes, but we specifically don't allow 'e' or 'E' here
|
||||
// since we want the scanner to treat that as the start of an
|
||||
// invalid keyword instead, to produce more intelligible error messages.
|
||||
case '-', '+', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func scanNumber(buf []byte, start pos) ([]byte, []byte, pos) {
|
||||
// The scanner doesn't check that the sequence of digit-ish bytes is
|
||||
// in a valid order. The parser must do this when decoding a number
|
||||
// token.
|
||||
var i int
|
||||
p := start
|
||||
Byte:
|
||||
for i = 0; i < len(buf); i++ {
|
||||
switch buf[i] {
|
||||
case '-', '+', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
p.Pos.Byte++
|
||||
p.Pos.Column++
|
||||
default:
|
||||
break Byte
|
||||
}
|
||||
}
|
||||
return buf[:i], buf[i:], p
|
||||
}
|
||||
|
||||
func byteCanStartKeyword(b byte) bool {
|
||||
switch {
|
||||
// We allow any sequence of alphabetical characters here, even though
|
||||
// JSON is more constrained, so that we can collect what we presume
|
||||
// the user intended to be a single keyword and then check its validity
|
||||
// in the parser, where we can generate better diagnostics.
|
||||
// So e.g. we want to be able to say:
|
||||
// unrecognized keyword "True". Did you mean "true"?
|
||||
case isAlphabetical(b):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func scanKeyword(buf []byte, start pos) ([]byte, []byte, pos) {
|
||||
var i int
|
||||
p := start
|
||||
Byte:
|
||||
for i = 0; i < len(buf); i++ {
|
||||
b := buf[i]
|
||||
switch {
|
||||
case isAlphabetical(b) || b == '_':
|
||||
p.Pos.Byte++
|
||||
p.Pos.Column++
|
||||
default:
|
||||
break Byte
|
||||
}
|
||||
}
|
||||
return buf[:i], buf[i:], p
|
||||
}
|
||||
|
||||
func scanString(buf []byte, start pos) ([]byte, []byte, pos) {
|
||||
// The scanner doesn't validate correct use of escapes, etc. It pays
|
||||
// attention to escapes only for the purpose of identifying the closing
|
||||
// quote character. It's the parser's responsibility to do proper
|
||||
// validation.
|
||||
//
|
||||
// The scanner also doesn't specifically detect unterminated string
|
||||
// literals, though they can be identified in the parser by checking if
|
||||
// the final byte in a string token is the double-quote character.
|
||||
|
||||
// Skip the opening quote symbol
|
||||
i := 1
|
||||
p := start
|
||||
p.Pos.Byte++
|
||||
p.Pos.Column++
|
||||
escaping := false
|
||||
Byte:
|
||||
for i < len(buf) {
|
||||
b := buf[i]
|
||||
|
||||
switch {
|
||||
case b == '\\':
|
||||
escaping = !escaping
|
||||
p.Pos.Byte++
|
||||
p.Pos.Column++
|
||||
i++
|
||||
case b == '"':
|
||||
p.Pos.Byte++
|
||||
p.Pos.Column++
|
||||
i++
|
||||
if !escaping {
|
||||
break Byte
|
||||
}
|
||||
escaping = false
|
||||
case b < 32:
|
||||
break Byte
|
||||
default:
|
||||
// Advance by one grapheme cluster, so that we consider each
|
||||
// grapheme to be a "column".
|
||||
// Ignoring error because this scanner cannot produce errors.
|
||||
advance, _, _ := textseg.ScanGraphemeClusters(buf[i:], true)
|
||||
|
||||
p.Pos.Byte += advance
|
||||
p.Pos.Column++
|
||||
i += advance
|
||||
|
||||
escaping = false
|
||||
}
|
||||
}
|
||||
return buf[:i], buf[i:], p
|
||||
}
|
||||
|
||||
func skipWhitespace(buf []byte, start pos) ([]byte, pos) {
|
||||
var i int
|
||||
p := start
|
||||
Byte:
|
||||
for i = 0; i < len(buf); i++ {
|
||||
switch buf[i] {
|
||||
case ' ':
|
||||
p.Pos.Byte++
|
||||
p.Pos.Column++
|
||||
case '\n':
|
||||
p.Pos.Byte++
|
||||
p.Pos.Column = 1
|
||||
p.Pos.Line++
|
||||
case '\r':
|
||||
// For the purpose of line/column counting we consider a
|
||||
// carriage return to take up no space, assuming that it will
|
||||
// be paired up with a newline (on Windows, for example) that
|
||||
// will account for both of them.
|
||||
p.Pos.Byte++
|
||||
case '\t':
|
||||
// We arbitrarily count a tab as if it were two spaces, because
|
||||
// we need to choose _some_ number here. This means any system
|
||||
// that renders code on-screen with markers must itself treat
|
||||
// tabs as a pair of spaces for rendering purposes, or instead
|
||||
// use the byte offset and back into its own column position.
|
||||
p.Pos.Byte++
|
||||
p.Pos.Column += 2
|
||||
default:
|
||||
break Byte
|
||||
}
|
||||
}
|
||||
return buf[i:], p
|
||||
}
|
||||
|
||||
type pos struct {
|
||||
Filename string
|
||||
Pos hcl.Pos
|
||||
}
|
||||
|
||||
func (p *pos) Range(byteLen, charLen int) hcl.Range {
|
||||
start := p.Pos
|
||||
end := p.Pos
|
||||
end.Byte += byteLen
|
||||
end.Column += charLen
|
||||
return hcl.Range{
|
||||
Filename: p.Filename,
|
||||
Start: start,
|
||||
End: end,
|
||||
}
|
||||
}
|
||||
|
||||
func posRange(start, end pos) hcl.Range {
|
||||
return hcl.Range{
|
||||
Filename: start.Filename,
|
||||
Start: start.Pos,
|
||||
End: end.Pos,
|
||||
}
|
||||
}
|
||||
|
||||
func (t token) GoString() string {
|
||||
return fmt.Sprintf("json.token{json.%s, []byte(%q), %#v}", t.Type, t.Bytes, t.Range)
|
||||
}
|
||||
|
||||
func isAlphabetical(b byte) bool {
|
||||
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
|
||||
}
|
405
vendor/github.com/hashicorp/hcl/v2/json/spec.md
generated
vendored
Normal file
405
vendor/github.com/hashicorp/hcl/v2/json/spec.md
generated
vendored
Normal file
@ -0,0 +1,405 @@
|
||||
# HCL JSON Syntax Specification
|
||||
|
||||
This is the specification for the JSON serialization for hcl. HCL is a system
|
||||
for defining configuration languages for applications. The HCL information
|
||||
model is designed to support multiple concrete syntaxes for configuration,
|
||||
and this JSON-based format complements [the native syntax](../hclsyntax/spec.md)
|
||||
by being easy to machine-generate, whereas the native syntax is oriented
|
||||
towards human authoring and maintenance
|
||||
|
||||
This syntax is defined in terms of JSON as defined in
|
||||
[RFC7159](https://tools.ietf.org/html/rfc7159). As such it inherits the JSON
|
||||
grammar as-is, and merely defines a specific methodology for interpreting
|
||||
JSON constructs into HCL structural elements and expressions.
|
||||
|
||||
This mapping is defined such that valid JSON-serialized HCL input can be
|
||||
_produced_ using standard JSON implementations in various programming languages.
|
||||
_Parsing_ such JSON has some additional constraints not beyond what is normally
|
||||
supported by JSON parsers, so a specialized parser may be required that
|
||||
is able to:
|
||||
|
||||
- Preserve the relative ordering of properties defined in an object.
|
||||
- Preserve multiple definitions of the same property name.
|
||||
- Preserve numeric values to the precision required by the number type
|
||||
in [the HCL syntax-agnostic information model](../spec.md).
|
||||
- Retain source location information for parsed tokens/constructs in order
|
||||
to produce good error messages.
|
||||
|
||||
## Structural Elements
|
||||
|
||||
[The HCL syntax-agnostic information model](../spec.md) defines a _body_ as an
|
||||
abstract container for attribute definitions and child blocks. A body is
|
||||
represented in JSON as either a single JSON object or a JSON array of objects.
|
||||
|
||||
Body processing is in terms of JSON object properties, visited in the order
|
||||
they appear in the input. Where a body is represented by a single JSON object,
|
||||
the properties of that object are visited in order. Where a body is
|
||||
represented by a JSON array, each of its elements are visited in order and
|
||||
each element has its properties visited in order. If any element of the array
|
||||
is not a JSON object then the input is erroneous.
|
||||
|
||||
When a body is being processed in the _dynamic attributes_ mode, the allowance
|
||||
of a JSON array in the previous paragraph does not apply and instead a single
|
||||
JSON object is always required.
|
||||
|
||||
As defined in the language-agnostic model, body processing is in terms
|
||||
of a schema which provides context for interpreting the body's content. For
|
||||
JSON bodies, the schema is crucial to allow differentiation of attribute
|
||||
definitions and block definitions, both of which are represented via object
|
||||
properties.
|
||||
|
||||
The special property name `"//"`, when used in an object representing a HCL
|
||||
body, is parsed and ignored. A property with this name can be used to
|
||||
include human-readable comments. (This special property name is _not_
|
||||
processed in this way for any _other_ HCL constructs that are represented as
|
||||
JSON objects.)
|
||||
|
||||
### Attributes
|
||||
|
||||
Where the given schema describes an attribute with a given name, the object
|
||||
property with the matching name — if present — serves as the attribute's
|
||||
definition.
|
||||
|
||||
When a body is being processed in the _dynamic attributes_ mode, each object
|
||||
property serves as an attribute definition for the attribute whose name
|
||||
matches the property name.
|
||||
|
||||
The value of an attribute definition property is interpreted as an _expression_,
|
||||
as described in a later section.
|
||||
|
||||
Given a schema that calls for an attribute named "foo", a JSON object like
|
||||
the following provides a definition for that attribute:
|
||||
|
||||
```json
|
||||
{
|
||||
"foo": "bar baz"
|
||||
}
|
||||
```
|
||||
|
||||
### Blocks
|
||||
|
||||
Where the given schema describes a block with a given type name, each object
|
||||
property with the matching name serves as a definition of zero or more blocks
|
||||
of that type.
|
||||
|
||||
Processing of child blocks is in terms of nested JSON objects and arrays.
|
||||
If the schema defines one or more _labels_ for the block type, a nested JSON
|
||||
object or JSON array of objects is required for each labelling level. These
|
||||
are flattened to a single ordered sequence of object properties using the
|
||||
same algorithm as for body content as defined above. Each object property
|
||||
serves as a label value at the corresponding level.
|
||||
|
||||
After any labelling levels, the next nested value is either a JSON object
|
||||
representing a single block body, or a JSON array of JSON objects that each
|
||||
represent a single block body. Use of an array accommodates the definition
|
||||
of multiple blocks that have identical type and labels.
|
||||
|
||||
Given a schema that calls for a block type named "foo" with no labels, the
|
||||
following JSON objects are all valid definitions of zero or more blocks of this
|
||||
type:
|
||||
|
||||
```json
|
||||
{
|
||||
"foo": {
|
||||
"child_attr": "baz"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"foo": [
|
||||
{
|
||||
"child_attr": "baz"
|
||||
},
|
||||
{
|
||||
"child_attr": "boz"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"foo": []
|
||||
}
|
||||
```
|
||||
|
||||
The first of these defines a single child block of type "foo". The second
|
||||
defines _two_ such blocks. The final example shows a degenerate definition
|
||||
of zero blocks, though generators should prefer to omit the property entirely
|
||||
in this scenario.
|
||||
|
||||
Given a schema that calls for a block type named "foo" with _two_ labels, the
|
||||
extra label levels must be represented as objects or arrays of objects as in
|
||||
the following examples:
|
||||
|
||||
```json
|
||||
{
|
||||
"foo": {
|
||||
"bar": {
|
||||
"baz": {
|
||||
"child_attr": "baz"
|
||||
},
|
||||
"boz": {
|
||||
"child_attr": "baz"
|
||||
}
|
||||
},
|
||||
"boz": {
|
||||
"baz": {
|
||||
"child_attr": "baz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"foo": {
|
||||
"bar": {
|
||||
"baz": {
|
||||
"child_attr": "baz"
|
||||
},
|
||||
"boz": {
|
||||
"child_attr": "baz"
|
||||
}
|
||||
},
|
||||
"boz": {
|
||||
"baz": [
|
||||
{
|
||||
"child_attr": "baz"
|
||||
},
|
||||
{
|
||||
"child_attr": "boz"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"foo": [
|
||||
{
|
||||
"bar": {
|
||||
"baz": {
|
||||
"child_attr": "baz"
|
||||
},
|
||||
"boz": {
|
||||
"child_attr": "baz"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"bar": {
|
||||
"baz": [
|
||||
{
|
||||
"child_attr": "baz"
|
||||
},
|
||||
{
|
||||
"child_attr": "boz"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"foo": {
|
||||
"bar": {
|
||||
"baz": {
|
||||
"child_attr": "baz"
|
||||
},
|
||||
"boz": {
|
||||
"child_attr": "baz"
|
||||
}
|
||||
},
|
||||
"bar": {
|
||||
"baz": [
|
||||
{
|
||||
"child_attr": "baz"
|
||||
},
|
||||
{
|
||||
"child_attr": "boz"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Arrays can be introduced at either the label definition or block body
|
||||
definition levels to define multiple definitions of the same block type
|
||||
or labels while preserving order.
|
||||
|
||||
A JSON HCL parser _must_ support duplicate definitions of the same property
|
||||
name within a single object, preserving all of them and the relative ordering
|
||||
between them. The array-based forms are also required so that JSON HCL
|
||||
configurations can be produced with JSON producing libraries that are not
|
||||
able to preserve property definition order and multiple definitions of
|
||||
the same property.
|
||||
|
||||
## Expressions
|
||||
|
||||
JSON lacks a native expression syntax, so the HCL JSON syntax instead defines
|
||||
a mapping for each of the JSON value types, including a special mapping for
|
||||
strings that allows optional use of arbitrary expressions.
|
||||
|
||||
### Objects
|
||||
|
||||
When interpreted as an expression, a JSON object represents a value of a HCL
|
||||
object type.
|
||||
|
||||
Each property of the JSON object represents an attribute of the HCL object type.
|
||||
The property name string given in the JSON input is interpreted as a string
|
||||
expression as described below, and its result is converted to string as defined
|
||||
by the syntax-agnostic information model. If such a conversion is not possible,
|
||||
an error is produced and evaluation fails.
|
||||
|
||||
An instance of the constructed object type is then created, whose values
|
||||
are interpreted by again recursively applying the mapping rules defined in
|
||||
this section to each of the property values.
|
||||
|
||||
If any evaluated property name strings produce null values, an error is
|
||||
produced and evaluation fails. If any produce _unknown_ values, the _entire
|
||||
object's_ result is an unknown value of the dynamic pseudo-type, signalling
|
||||
that the type of the object cannot be determined.
|
||||
|
||||
It is an error to define the same property name multiple times within a single
|
||||
JSON object interpreted as an expression. In full expression mode, this
|
||||
constraint applies to the name expression results after conversion to string,
|
||||
rather than the raw string that may contain interpolation expressions.
|
||||
|
||||
### Arrays
|
||||
|
||||
When interpreted as an expression, a JSON array represents a value of a HCL
|
||||
tuple type.
|
||||
|
||||
Each element of the JSON array represents an element of the HCL tuple type.
|
||||
The tuple type is constructed by enumerating the JSON array elements, creating
|
||||
for each an element whose type is the result of recursively applying the
|
||||
expression mapping rules. Correspondence is preserved between the array element
|
||||
indices and the tuple element indices.
|
||||
|
||||
An instance of the constructed tuple type is then created, whose values are
|
||||
interpreted by again recursively applying the mapping rules defined in this
|
||||
section.
|
||||
|
||||
### Numbers
|
||||
|
||||
When interpreted as an expression, a JSON number represents a HCL number value.
|
||||
|
||||
HCL numbers are arbitrary-precision decimal values, so a JSON HCL parser must
|
||||
be able to translate exactly the value given to a number of corresponding
|
||||
precision, within the constraints set by the HCL syntax-agnostic information
|
||||
model.
|
||||
|
||||
In practice, off-the-shelf JSON serializers often do not support customizing the
|
||||
processing of numbers, and instead force processing as 32-bit or 64-bit
|
||||
floating point values.
|
||||
|
||||
A _producer_ of JSON HCL that uses such a serializer can provide numeric values
|
||||
as JSON strings where they have precision too great for representation in the
|
||||
serializer's chosen numeric type in situations where the result will be
|
||||
converted to number (using the standard conversion rules) by a calling
|
||||
application.
|
||||
|
||||
Alternatively, for expressions that are evaluated in full expression mode an
|
||||
embedded template interpolation can be used to faithfully represent a number,
|
||||
such as `"${1e150}"`, which will then be evaluated by the underlying HCL native
|
||||
syntax expression evaluator.
|
||||
|
||||
### Boolean Values
|
||||
|
||||
The JSON boolean values `true` and `false`, when interpreted as expressions,
|
||||
represent the corresponding HCL boolean values.
|
||||
|
||||
### The Null Value
|
||||
|
||||
The JSON value `null`, when interpreted as an expression, represents a
|
||||
HCL null value of the dynamic pseudo-type.
|
||||
|
||||
### Strings
|
||||
|
||||
When interpreted as an expression, a JSON string may be interpreted in one of
|
||||
two ways depending on the evaluation mode.
|
||||
|
||||
If evaluating in literal-only mode (as defined by the syntax-agnostic
|
||||
information model) the literal string is intepreted directly as a HCL string
|
||||
value, by directly using the exact sequence of unicode characters represented.
|
||||
Template interpolations and directives MUST NOT be processed in this mode,
|
||||
allowing any characters that appear as introduction sequences to pass through
|
||||
literally:
|
||||
|
||||
```json
|
||||
"Hello world! Template sequences like ${ are not intepreted here."
|
||||
```
|
||||
|
||||
When evaluating in full expression mode (again, as defined by the syntax-
|
||||
agnostic information model) the literal string is instead interpreted as a
|
||||
_standalone template_ in the HCL Native Syntax. The expression evaluation
|
||||
result is then the direct result of evaluating that template with the current
|
||||
variable scope and function table.
|
||||
|
||||
```json
|
||||
"Hello, ${name}! Template sequences are interpreted in full expression mode."
|
||||
```
|
||||
|
||||
In particular the _Template Interpolation Unwrapping_ requirement from the
|
||||
HCL native syntax specification must be implemented, allowing the use of
|
||||
single-interpolation templates to represent expressions that would not
|
||||
otherwise be representable in JSON, such as the following example where
|
||||
the result must be a number, rather than a string representation of a number:
|
||||
|
||||
```json
|
||||
"${ a + b }"
|
||||
```
|
||||
|
||||
## Static Analysis
|
||||
|
||||
The HCL static analysis operations are implemented for JSON values that
|
||||
represent expressions, as described in the following sections.
|
||||
|
||||
Due to the limited expressive power of the JSON syntax alone, use of these
|
||||
static analyses functions rather than normal expression evaluation is used
|
||||
as additional context for how a JSON value is to be interpreted, which means
|
||||
that static analyses can result in a different interpretation of a given
|
||||
expression than normal evaluation.
|
||||
|
||||
### Static List
|
||||
|
||||
An expression interpreted as a static list must be a JSON array. Each of the
|
||||
values in the array is interpreted as an expression and returned.
|
||||
|
||||
### Static Map
|
||||
|
||||
An expression interpreted as a static map must be a JSON object. Each of the
|
||||
key/value pairs in the object is presented as a pair of expressions. Since
|
||||
object property names are always strings, evaluating the key expression with
|
||||
a non-`nil` evaluation context will evaluate any template sequences given
|
||||
in the property name.
|
||||
|
||||
### Static Call
|
||||
|
||||
An expression interpreted as a static call must be a string. The content of
|
||||
the string is interpreted as a native syntax expression (not a _template_,
|
||||
unlike normal evaluation) and then the static call analysis is delegated to
|
||||
that expression.
|
||||
|
||||
If the original expression is not a string or its contents cannot be parsed
|
||||
as a native syntax expression then static call analysis is not supported.
|
||||
|
||||
### Static Traversal
|
||||
|
||||
An expression interpreted as a static traversal must be a string. The content
|
||||
of the string is interpreted as a native syntax expression (not a _template_,
|
||||
unlike normal evaluation) and then static traversal analysis is delegated
|
||||
to that expression.
|
||||
|
||||
If the original expression is not a string or its contents cannot be parsed
|
||||
as a native syntax expression then static call analysis is not supported.
|
637
vendor/github.com/hashicorp/hcl/v2/json/structure.go
generated
vendored
Normal file
637
vendor/github.com/hashicorp/hcl/v2/json/structure.go
generated
vendored
Normal file
@ -0,0 +1,637 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
)
|
||||
|
||||
// body is the implementation of "Body" used for files processed with the JSON
|
||||
// parser.
|
||||
type body struct {
|
||||
val node
|
||||
|
||||
// If non-nil, the keys of this map cause the corresponding attributes to
|
||||
// be treated as non-existing. This is used when Body.PartialContent is
|
||||
// called, to produce the "remaining content" Body.
|
||||
hiddenAttrs map[string]struct{}
|
||||
}
|
||||
|
||||
// expression is the implementation of "Expression" used for files processed
|
||||
// with the JSON parser.
|
||||
type expression struct {
|
||||
src node
|
||||
}
|
||||
|
||||
func (b *body) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
|
||||
content, newBody, diags := b.PartialContent(schema)
|
||||
|
||||
hiddenAttrs := newBody.(*body).hiddenAttrs
|
||||
|
||||
var nameSuggestions []string
|
||||
for _, attrS := range schema.Attributes {
|
||||
if _, ok := hiddenAttrs[attrS.Name]; !ok {
|
||||
// Only suggest an attribute name if we didn't use it already.
|
||||
nameSuggestions = append(nameSuggestions, attrS.Name)
|
||||
}
|
||||
}
|
||||
for _, blockS := range schema.Blocks {
|
||||
// Blocks can appear multiple times, so we'll suggest their type
|
||||
// names regardless of whether they've already been used.
|
||||
nameSuggestions = append(nameSuggestions, blockS.Type)
|
||||
}
|
||||
|
||||
jsonAttrs, attrDiags := b.collectDeepAttrs(b.val, nil)
|
||||
diags = append(diags, attrDiags...)
|
||||
|
||||
for _, attr := range jsonAttrs {
|
||||
k := attr.Name
|
||||
if k == "//" {
|
||||
// Ignore "//" keys in objects representing bodies, to allow
|
||||
// their use as comments.
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := hiddenAttrs[k]; !ok {
|
||||
suggestion := nameSuggestion(k, nameSuggestions)
|
||||
if suggestion != "" {
|
||||
suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
|
||||
}
|
||||
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Extraneous JSON object property",
|
||||
Detail: fmt.Sprintf("No argument or block type is named %q.%s", k, suggestion),
|
||||
Subject: &attr.NameRange,
|
||||
Context: attr.Range().Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return content, diags
|
||||
}
|
||||
|
||||
func (b *body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
jsonAttrs, attrDiags := b.collectDeepAttrs(b.val, nil)
|
||||
diags = append(diags, attrDiags...)
|
||||
|
||||
usedNames := map[string]struct{}{}
|
||||
if b.hiddenAttrs != nil {
|
||||
for k := range b.hiddenAttrs {
|
||||
usedNames[k] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
content := &hcl.BodyContent{
|
||||
Attributes: map[string]*hcl.Attribute{},
|
||||
Blocks: nil,
|
||||
|
||||
MissingItemRange: b.MissingItemRange(),
|
||||
}
|
||||
|
||||
// Create some more convenient data structures for our work below.
|
||||
attrSchemas := map[string]hcl.AttributeSchema{}
|
||||
blockSchemas := map[string]hcl.BlockHeaderSchema{}
|
||||
for _, attrS := range schema.Attributes {
|
||||
attrSchemas[attrS.Name] = attrS
|
||||
}
|
||||
for _, blockS := range schema.Blocks {
|
||||
blockSchemas[blockS.Type] = blockS
|
||||
}
|
||||
|
||||
for _, jsonAttr := range jsonAttrs {
|
||||
attrName := jsonAttr.Name
|
||||
if _, used := b.hiddenAttrs[attrName]; used {
|
||||
continue
|
||||
}
|
||||
|
||||
if attrS, defined := attrSchemas[attrName]; defined {
|
||||
if existing, exists := content.Attributes[attrName]; exists {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Duplicate argument",
|
||||
Detail: fmt.Sprintf("The argument %q was already set at %s.", attrName, existing.Range),
|
||||
Subject: &jsonAttr.NameRange,
|
||||
Context: jsonAttr.Range().Ptr(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
content.Attributes[attrS.Name] = &hcl.Attribute{
|
||||
Name: attrS.Name,
|
||||
Expr: &expression{src: jsonAttr.Value},
|
||||
Range: hcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
|
||||
NameRange: jsonAttr.NameRange,
|
||||
}
|
||||
usedNames[attrName] = struct{}{}
|
||||
|
||||
} else if blockS, defined := blockSchemas[attrName]; defined {
|
||||
bv := jsonAttr.Value
|
||||
blockDiags := b.unpackBlock(bv, blockS.Type, &jsonAttr.NameRange, blockS.LabelNames, nil, nil, &content.Blocks)
|
||||
diags = append(diags, blockDiags...)
|
||||
usedNames[attrName] = struct{}{}
|
||||
}
|
||||
|
||||
// We ignore anything that isn't defined because that's the
|
||||
// PartialContent contract. The Content method will catch leftovers.
|
||||
}
|
||||
|
||||
// Make sure we got all the required attributes.
|
||||
for _, attrS := range schema.Attributes {
|
||||
if !attrS.Required {
|
||||
continue
|
||||
}
|
||||
if _, defined := content.Attributes[attrS.Name]; !defined {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing required argument",
|
||||
Detail: fmt.Sprintf("The argument %q is required, but no definition was found.", attrS.Name),
|
||||
Subject: b.MissingItemRange().Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unusedBody := &body{
|
||||
val: b.val,
|
||||
hiddenAttrs: usedNames,
|
||||
}
|
||||
|
||||
return content, unusedBody, diags
|
||||
}
|
||||
|
||||
// JustAttributes for JSON bodies interprets all properties of the wrapped
|
||||
// JSON object as attributes and returns them.
|
||||
func (b *body) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
attrs := make(map[string]*hcl.Attribute)
|
||||
|
||||
obj, ok := b.val.(*objectVal)
|
||||
if !ok {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Incorrect JSON value type",
|
||||
Detail: "A JSON object is required here, setting the arguments for this block.",
|
||||
Subject: b.val.StartRange().Ptr(),
|
||||
})
|
||||
return attrs, diags
|
||||
}
|
||||
|
||||
for _, jsonAttr := range obj.Attrs {
|
||||
name := jsonAttr.Name
|
||||
if name == "//" {
|
||||
// Ignore "//" keys in objects representing bodies, to allow
|
||||
// their use as comments.
|
||||
continue
|
||||
}
|
||||
|
||||
if _, hidden := b.hiddenAttrs[name]; hidden {
|
||||
continue
|
||||
}
|
||||
|
||||
if existing, exists := attrs[name]; exists {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Duplicate attribute definition",
|
||||
Detail: fmt.Sprintf("The argument %q was already set at %s.", name, existing.Range),
|
||||
Subject: &jsonAttr.NameRange,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
attrs[name] = &hcl.Attribute{
|
||||
Name: name,
|
||||
Expr: &expression{src: jsonAttr.Value},
|
||||
Range: hcl.RangeBetween(jsonAttr.NameRange, jsonAttr.Value.Range()),
|
||||
NameRange: jsonAttr.NameRange,
|
||||
}
|
||||
}
|
||||
|
||||
// No diagnostics possible here, since the parser already took care of
|
||||
// finding duplicates and every JSON value can be a valid attribute value.
|
||||
return attrs, diags
|
||||
}
|
||||
|
||||
func (b *body) MissingItemRange() hcl.Range {
|
||||
switch tv := b.val.(type) {
|
||||
case *objectVal:
|
||||
return tv.CloseRange
|
||||
case *arrayVal:
|
||||
return tv.OpenRange
|
||||
default:
|
||||
// Should not happen in correct operation, but might show up if the
|
||||
// input is invalid and we are producing partial results.
|
||||
return tv.StartRange()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *body) unpackBlock(v node, typeName string, typeRange *hcl.Range, labelsLeft []string, labelsUsed []string, labelRanges []hcl.Range, blocks *hcl.Blocks) (diags hcl.Diagnostics) {
|
||||
if len(labelsLeft) > 0 {
|
||||
labelName := labelsLeft[0]
|
||||
jsonAttrs, attrDiags := b.collectDeepAttrs(v, &labelName)
|
||||
diags = append(diags, attrDiags...)
|
||||
|
||||
if len(jsonAttrs) == 0 {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing block label",
|
||||
Detail: fmt.Sprintf("At least one object property is required, whose name represents the %s block's %s.", typeName, labelName),
|
||||
Subject: v.StartRange().Ptr(),
|
||||
})
|
||||
return
|
||||
}
|
||||
labelsUsed := append(labelsUsed, "")
|
||||
labelRanges := append(labelRanges, hcl.Range{})
|
||||
for _, p := range jsonAttrs {
|
||||
pk := p.Name
|
||||
labelsUsed[len(labelsUsed)-1] = pk
|
||||
labelRanges[len(labelRanges)-1] = p.NameRange
|
||||
diags = append(diags, b.unpackBlock(p.Value, typeName, typeRange, labelsLeft[1:], labelsUsed, labelRanges, blocks)...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// By the time we get here, we've peeled off all the labels and we're ready
|
||||
// to deal with the block's actual content.
|
||||
|
||||
// need to copy the label slices because their underlying arrays will
|
||||
// continue to be mutated after we return.
|
||||
labels := make([]string, len(labelsUsed))
|
||||
copy(labels, labelsUsed)
|
||||
labelR := make([]hcl.Range, len(labelRanges))
|
||||
copy(labelR, labelRanges)
|
||||
|
||||
switch tv := v.(type) {
|
||||
case *nullVal:
|
||||
// There is no block content, e.g the value is null.
|
||||
return
|
||||
case *objectVal:
|
||||
// Single instance of the block
|
||||
*blocks = append(*blocks, &hcl.Block{
|
||||
Type: typeName,
|
||||
Labels: labels,
|
||||
Body: &body{
|
||||
val: tv,
|
||||
},
|
||||
|
||||
DefRange: tv.OpenRange,
|
||||
TypeRange: *typeRange,
|
||||
LabelRanges: labelR,
|
||||
})
|
||||
case *arrayVal:
|
||||
// Multiple instances of the block
|
||||
for _, av := range tv.Values {
|
||||
*blocks = append(*blocks, &hcl.Block{
|
||||
Type: typeName,
|
||||
Labels: labels,
|
||||
Body: &body{
|
||||
val: av, // might be mistyped; we'll find out when content is requested for this body
|
||||
},
|
||||
|
||||
DefRange: tv.OpenRange,
|
||||
TypeRange: *typeRange,
|
||||
LabelRanges: labelR,
|
||||
})
|
||||
}
|
||||
default:
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Incorrect JSON value type",
|
||||
Detail: fmt.Sprintf("Either a JSON object or a JSON array is required, representing the contents of one or more %q blocks.", typeName),
|
||||
Subject: v.StartRange().Ptr(),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// collectDeepAttrs takes either a single object or an array of objects and
|
||||
// flattens it into a list of object attributes, collecting attributes from
|
||||
// all of the objects in a given array.
|
||||
//
|
||||
// Ordering is preserved, so a list of objects that each have one property
|
||||
// will result in those properties being returned in the same order as the
|
||||
// objects appeared in the array.
|
||||
//
|
||||
// This is appropriate for use only for objects representing bodies or labels
|
||||
// within a block.
|
||||
//
|
||||
// The labelName argument, if non-null, is used to tailor returned error
|
||||
// messages to refer to block labels rather than attributes and child blocks.
|
||||
// It has no other effect.
|
||||
func (b *body) collectDeepAttrs(v node, labelName *string) ([]*objectAttr, hcl.Diagnostics) {
|
||||
var diags hcl.Diagnostics
|
||||
var attrs []*objectAttr
|
||||
|
||||
switch tv := v.(type) {
|
||||
case *nullVal:
|
||||
// If a value is null, then we don't return any attributes or return an error.
|
||||
|
||||
case *objectVal:
|
||||
attrs = append(attrs, tv.Attrs...)
|
||||
|
||||
case *arrayVal:
|
||||
for _, ev := range tv.Values {
|
||||
switch tev := ev.(type) {
|
||||
case *objectVal:
|
||||
attrs = append(attrs, tev.Attrs...)
|
||||
default:
|
||||
if labelName != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Incorrect JSON value type",
|
||||
Detail: fmt.Sprintf("A JSON object is required here, to specify %s labels for this block.", *labelName),
|
||||
Subject: ev.StartRange().Ptr(),
|
||||
})
|
||||
} else {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Incorrect JSON value type",
|
||||
Detail: "A JSON object is required here, to define arguments and child blocks.",
|
||||
Subject: ev.StartRange().Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
if labelName != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Incorrect JSON value type",
|
||||
Detail: fmt.Sprintf("Either a JSON object or JSON array of objects is required here, to specify %s labels for this block.", *labelName),
|
||||
Subject: v.StartRange().Ptr(),
|
||||
})
|
||||
} else {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Incorrect JSON value type",
|
||||
Detail: "Either a JSON object or JSON array of objects is required here, to define arguments and child blocks.",
|
||||
Subject: v.StartRange().Ptr(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return attrs, diags
|
||||
}
|
||||
|
||||
func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
switch v := e.src.(type) {
|
||||
case *stringVal:
|
||||
if ctx != nil {
|
||||
// Parse string contents as a HCL native language expression.
|
||||
// We only do this if we have a context, so passing a nil context
|
||||
// is how the caller specifies that interpolations are not allowed
|
||||
// and that the string should just be returned verbatim.
|
||||
templateSrc := v.Value
|
||||
expr, diags := hclsyntax.ParseTemplate(
|
||||
[]byte(templateSrc),
|
||||
v.SrcRange.Filename,
|
||||
|
||||
// This won't produce _exactly_ the right result, since
|
||||
// the hclsyntax parser can't "see" any escapes we removed
|
||||
// while parsing JSON, but it's better than nothing.
|
||||
hcl.Pos{
|
||||
Line: v.SrcRange.Start.Line,
|
||||
|
||||
// skip over the opening quote mark
|
||||
Byte: v.SrcRange.Start.Byte + 1,
|
||||
Column: v.SrcRange.Start.Column + 1,
|
||||
},
|
||||
)
|
||||
if diags.HasErrors() {
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
val, evalDiags := expr.Value(ctx)
|
||||
diags = append(diags, evalDiags...)
|
||||
return val, diags
|
||||
}
|
||||
|
||||
return cty.StringVal(v.Value), nil
|
||||
case *numberVal:
|
||||
return cty.NumberVal(v.Value), nil
|
||||
case *booleanVal:
|
||||
return cty.BoolVal(v.Value), nil
|
||||
case *arrayVal:
|
||||
var diags hcl.Diagnostics
|
||||
vals := []cty.Value{}
|
||||
for _, jsonVal := range v.Values {
|
||||
val, valDiags := (&expression{src: jsonVal}).Value(ctx)
|
||||
vals = append(vals, val)
|
||||
diags = append(diags, valDiags...)
|
||||
}
|
||||
return cty.TupleVal(vals), diags
|
||||
case *objectVal:
|
||||
var diags hcl.Diagnostics
|
||||
attrs := map[string]cty.Value{}
|
||||
attrRanges := map[string]hcl.Range{}
|
||||
known := true
|
||||
for _, jsonAttr := range v.Attrs {
|
||||
// In this one context we allow keys to contain interpolation
|
||||
// expressions too, assuming we're evaluating in interpolation
|
||||
// mode. This achieves parity with the native syntax where
|
||||
// object expressions can have dynamic keys, while block contents
|
||||
// may not.
|
||||
name, nameDiags := (&expression{src: &stringVal{
|
||||
Value: jsonAttr.Name,
|
||||
SrcRange: jsonAttr.NameRange,
|
||||
}}).Value(ctx)
|
||||
valExpr := &expression{src: jsonAttr.Value}
|
||||
val, valDiags := valExpr.Value(ctx)
|
||||
diags = append(diags, nameDiags...)
|
||||
diags = append(diags, valDiags...)
|
||||
|
||||
var err error
|
||||
name, err = convert.Convert(name, cty.String)
|
||||
if err != nil {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid object key expression",
|
||||
Detail: fmt.Sprintf("Cannot use this expression as an object key: %s.", err),
|
||||
Subject: &jsonAttr.NameRange,
|
||||
Expression: valExpr,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if name.IsNull() {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid object key expression",
|
||||
Detail: "Cannot use null value as an object key.",
|
||||
Subject: &jsonAttr.NameRange,
|
||||
Expression: valExpr,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
continue
|
||||
}
|
||||
if !name.IsKnown() {
|
||||
// This is a bit of a weird case, since our usual rules require
|
||||
// us to tolerate unknowns and just represent the result as
|
||||
// best we can but if we don't know the key then we can't
|
||||
// know the type of our object at all, and thus we must turn
|
||||
// the whole thing into cty.DynamicVal. This is consistent with
|
||||
// how this situation is handled in the native syntax.
|
||||
// We'll keep iterating so we can collect other errors in
|
||||
// subsequent attributes.
|
||||
known = false
|
||||
continue
|
||||
}
|
||||
nameStr := name.AsString()
|
||||
if _, defined := attrs[nameStr]; defined {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Duplicate object attribute",
|
||||
Detail: fmt.Sprintf("An attribute named %q was already defined at %s.", nameStr, attrRanges[nameStr]),
|
||||
Subject: &jsonAttr.NameRange,
|
||||
Expression: e,
|
||||
EvalContext: ctx,
|
||||
})
|
||||
continue
|
||||
}
|
||||
attrs[nameStr] = val
|
||||
attrRanges[nameStr] = jsonAttr.NameRange
|
||||
}
|
||||
if !known {
|
||||
// We encountered an unknown key somewhere along the way, so
|
||||
// we can't know what our type will eventually be.
|
||||
return cty.DynamicVal, diags
|
||||
}
|
||||
return cty.ObjectVal(attrs), diags
|
||||
case *nullVal:
|
||||
return cty.NullVal(cty.DynamicPseudoType), nil
|
||||
default:
|
||||
// Default to DynamicVal so that ASTs containing invalid nodes can
|
||||
// still be partially-evaluated.
|
||||
return cty.DynamicVal, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (e *expression) Variables() []hcl.Traversal {
|
||||
var vars []hcl.Traversal
|
||||
|
||||
switch v := e.src.(type) {
|
||||
case *stringVal:
|
||||
templateSrc := v.Value
|
||||
expr, diags := hclsyntax.ParseTemplate(
|
||||
[]byte(templateSrc),
|
||||
v.SrcRange.Filename,
|
||||
|
||||
// This won't produce _exactly_ the right result, since
|
||||
// the hclsyntax parser can't "see" any escapes we removed
|
||||
// while parsing JSON, but it's better than nothing.
|
||||
hcl.Pos{
|
||||
Line: v.SrcRange.Start.Line,
|
||||
|
||||
// skip over the opening quote mark
|
||||
Byte: v.SrcRange.Start.Byte + 1,
|
||||
Column: v.SrcRange.Start.Column + 1,
|
||||
},
|
||||
)
|
||||
if diags.HasErrors() {
|
||||
return vars
|
||||
}
|
||||
return expr.Variables()
|
||||
|
||||
case *arrayVal:
|
||||
for _, jsonVal := range v.Values {
|
||||
vars = append(vars, (&expression{src: jsonVal}).Variables()...)
|
||||
}
|
||||
case *objectVal:
|
||||
for _, jsonAttr := range v.Attrs {
|
||||
keyExpr := &stringVal{ // we're going to treat key as an expression in this context
|
||||
Value: jsonAttr.Name,
|
||||
SrcRange: jsonAttr.NameRange,
|
||||
}
|
||||
vars = append(vars, (&expression{src: keyExpr}).Variables()...)
|
||||
vars = append(vars, (&expression{src: jsonAttr.Value}).Variables()...)
|
||||
}
|
||||
}
|
||||
|
||||
return vars
|
||||
}
|
||||
|
||||
func (e *expression) Range() hcl.Range {
|
||||
return e.src.Range()
|
||||
}
|
||||
|
||||
func (e *expression) StartRange() hcl.Range {
|
||||
return e.src.StartRange()
|
||||
}
|
||||
|
||||
// Implementation for hcl.AbsTraversalForExpr.
|
||||
func (e *expression) AsTraversal() hcl.Traversal {
|
||||
// In JSON-based syntax a traversal is given as a string containing
|
||||
// traversal syntax as defined by hclsyntax.ParseTraversalAbs.
|
||||
|
||||
switch v := e.src.(type) {
|
||||
case *stringVal:
|
||||
traversal, diags := hclsyntax.ParseTraversalAbs([]byte(v.Value), v.SrcRange.Filename, v.SrcRange.Start)
|
||||
if diags.HasErrors() {
|
||||
return nil
|
||||
}
|
||||
return traversal
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation for hcl.ExprCall.
|
||||
func (e *expression) ExprCall() *hcl.StaticCall {
|
||||
// In JSON-based syntax a static call is given as a string containing
|
||||
// an expression in the native syntax that also supports ExprCall.
|
||||
|
||||
switch v := e.src.(type) {
|
||||
case *stringVal:
|
||||
expr, diags := hclsyntax.ParseExpression([]byte(v.Value), v.SrcRange.Filename, v.SrcRange.Start)
|
||||
if diags.HasErrors() {
|
||||
return nil
|
||||
}
|
||||
|
||||
call, diags := hcl.ExprCall(expr)
|
||||
if diags.HasErrors() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return call
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation for hcl.ExprList.
|
||||
func (e *expression) ExprList() []hcl.Expression {
|
||||
switch v := e.src.(type) {
|
||||
case *arrayVal:
|
||||
ret := make([]hcl.Expression, len(v.Values))
|
||||
for i, node := range v.Values {
|
||||
ret[i] = &expression{src: node}
|
||||
}
|
||||
return ret
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Implementation for hcl.ExprMap.
|
||||
func (e *expression) ExprMap() []hcl.KeyValuePair {
|
||||
switch v := e.src.(type) {
|
||||
case *objectVal:
|
||||
ret := make([]hcl.KeyValuePair, len(v.Attrs))
|
||||
for i, jsonAttr := range v.Attrs {
|
||||
ret[i] = hcl.KeyValuePair{
|
||||
Key: &expression{src: &stringVal{
|
||||
Value: jsonAttr.Name,
|
||||
SrcRange: jsonAttr.NameRange,
|
||||
}},
|
||||
Value: &expression{src: jsonAttr.Value},
|
||||
}
|
||||
}
|
||||
return ret
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
29
vendor/github.com/hashicorp/hcl/v2/json/tokentype_string.go
generated
vendored
Normal file
29
vendor/github.com/hashicorp/hcl/v2/json/tokentype_string.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
// Code generated by "stringer -type tokenType scanner.go"; DO NOT EDIT.
|
||||
|
||||
package json
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _tokenType_name = "tokenInvalidtokenCommatokenColontokenEqualstokenKeywordtokenNumbertokenStringtokenBrackOtokenBrackCtokenBraceOtokenBraceCtokenEOF"
|
||||
|
||||
var _tokenType_map = map[tokenType]string{
|
||||
0: _tokenType_name[0:12],
|
||||
44: _tokenType_name[12:22],
|
||||
58: _tokenType_name[22:32],
|
||||
61: _tokenType_name[32:43],
|
||||
75: _tokenType_name[43:55],
|
||||
78: _tokenType_name[55:66],
|
||||
83: _tokenType_name[66:77],
|
||||
91: _tokenType_name[77:88],
|
||||
93: _tokenType_name[88:99],
|
||||
123: _tokenType_name[99:110],
|
||||
125: _tokenType_name[110:121],
|
||||
9220: _tokenType_name[121:129],
|
||||
}
|
||||
|
||||
func (i tokenType) String() string {
|
||||
if str, ok := _tokenType_map[i]; ok {
|
||||
return str
|
||||
}
|
||||
return "tokenType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
Reference in New Issue
Block a user