govendor fetch github.com/hashicorp/hcl2/...
This just catches us up with the latest fixes and improvements in upstream HCL2.
This commit is contained in:
parent
13fa73c63e
commit
b865d62bb8
|
@ -2,7 +2,6 @@ package hcl
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -43,7 +42,7 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
|
|||
return errors.New("nil diagnostic")
|
||||
}
|
||||
|
||||
var colorCode, resetCode string
|
||||
var colorCode, highlightCode, resetCode string
|
||||
if w.color {
|
||||
switch diag.Severity {
|
||||
case DiagError:
|
||||
|
@ -52,6 +51,7 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
|
|||
colorCode = "\x1b[33m"
|
||||
}
|
||||
resetCode = "\x1b[0m"
|
||||
highlightCode = "\x1b[1;4m"
|
||||
}
|
||||
|
||||
var severityStr string
|
||||
|
@ -68,24 +68,31 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
|
|||
fmt.Fprintf(w.wr, "%s%s%s: %s\n\n", colorCode, severityStr, resetCode, diag.Summary)
|
||||
|
||||
if diag.Subject != nil {
|
||||
snipRange := *diag.Subject
|
||||
highlightRange := snipRange
|
||||
if diag.Context != nil {
|
||||
// Show enough of the source code to include both the subject
|
||||
// and context ranges, which overlap in all reasonable
|
||||
// situations.
|
||||
snipRange = RangeOver(snipRange, *diag.Context)
|
||||
}
|
||||
// We can't illustrate an empty range, so we'll turn such ranges into
|
||||
// single-character ranges, which might not be totally valid (may point
|
||||
// off the end of a line, or off the end of the file) but are good
|
||||
// enough for the bounds checks we do below.
|
||||
if snipRange.Empty() {
|
||||
snipRange.End.Byte++
|
||||
snipRange.End.Column++
|
||||
}
|
||||
if highlightRange.Empty() {
|
||||
highlightRange.End.Byte++
|
||||
highlightRange.End.Column++
|
||||
}
|
||||
|
||||
file := w.files[diag.Subject.Filename]
|
||||
if file == nil || file.Bytes == nil {
|
||||
fmt.Fprintf(w.wr, " on %s line %d:\n (source code not available)\n\n", diag.Subject.Filename, diag.Subject.Start.Line)
|
||||
} else {
|
||||
src := file.Bytes
|
||||
r := bytes.NewReader(src)
|
||||
sc := bufio.NewScanner(r)
|
||||
sc.Split(bufio.ScanLines)
|
||||
|
||||
var startLine, endLine int
|
||||
if diag.Context != nil {
|
||||
startLine = diag.Context.Start.Line
|
||||
endLine = diag.Context.End.Line
|
||||
} else {
|
||||
startLine = diag.Subject.Start.Line
|
||||
endLine = diag.Subject.End.Line
|
||||
}
|
||||
|
||||
var contextLine string
|
||||
if diag.Subject != nil {
|
||||
|
@ -95,35 +102,33 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error {
|
|||
}
|
||||
}
|
||||
|
||||
li := 1
|
||||
var ls string
|
||||
for sc.Scan() {
|
||||
ls = sc.Text()
|
||||
|
||||
if li == startLine {
|
||||
break
|
||||
}
|
||||
li++
|
||||
}
|
||||
|
||||
fmt.Fprintf(w.wr, " on %s line %d%s:\n", diag.Subject.Filename, diag.Subject.Start.Line, contextLine)
|
||||
|
||||
// TODO: Generate markers for the specific characters that are in the Context and Subject ranges.
|
||||
// For now, we just print out the lines.
|
||||
src := file.Bytes
|
||||
sc := NewRangeScanner(src, diag.Subject.Filename, bufio.ScanLines)
|
||||
|
||||
fmt.Fprintf(w.wr, "%4d: %s\n", li, ls)
|
||||
|
||||
if endLine > li {
|
||||
for sc.Scan() {
|
||||
ls = sc.Text()
|
||||
li++
|
||||
|
||||
fmt.Fprintf(w.wr, "%4d: %s\n", li, ls)
|
||||
|
||||
if li == endLine {
|
||||
break
|
||||
}
|
||||
for sc.Scan() {
|
||||
lineRange := sc.Range()
|
||||
if !lineRange.Overlaps(snipRange) {
|
||||
continue
|
||||
}
|
||||
|
||||
beforeRange, highlightedRange, afterRange := lineRange.PartitionAround(highlightRange)
|
||||
if highlightedRange.Empty() {
|
||||
fmt.Fprintf(w.wr, "%4d: %s\n", lineRange.Start.Line, sc.Bytes())
|
||||
} else {
|
||||
before := beforeRange.SliceBytes(src)
|
||||
highlighted := highlightedRange.SliceBytes(src)
|
||||
after := afterRange.SliceBytes(src)
|
||||
fmt.Fprintf(
|
||||
w.wr, "%4d: %s%s%s%s%s\n",
|
||||
lineRange.Start.Line,
|
||||
before,
|
||||
highlightCode, highlighted, resetCode,
|
||||
after,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
w.wr.Write([]byte{'\n'})
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package hcl
|
||||
|
||||
// ExprList tests if the given expression is a static list construct and,
|
||||
// if so, extracts the expressions that represent the list elements.
|
||||
// If the given expression is not a static list, error diagnostics are
|
||||
// returned.
|
||||
//
|
||||
// A particular Expression implementation can support this function by
|
||||
// offering a method called ExprList that takes no arguments and returns
|
||||
// []Expression. This method should return nil if a static list cannot
|
||||
// be extracted. Alternatively, an implementation can support
|
||||
// UnwrapExpression to delegate handling of this function to a wrapped
|
||||
// Expression object.
|
||||
func ExprList(expr Expression) ([]Expression, Diagnostics) {
|
||||
type exprList interface {
|
||||
ExprList() []Expression
|
||||
}
|
||||
|
||||
physExpr := UnwrapExpressionUntil(expr, func(expr Expression) bool {
|
||||
_, supported := expr.(exprList)
|
||||
return supported
|
||||
})
|
||||
|
||||
if exL, supported := physExpr.(exprList); supported {
|
||||
if list := exL.ExprList(); list != nil {
|
||||
return list, nil
|
||||
}
|
||||
}
|
||||
return nil, Diagnostics{
|
||||
&Diagnostic{
|
||||
Severity: DiagError,
|
||||
Summary: "Invalid expression",
|
||||
Detail: "A static list expression is required.",
|
||||
Subject: expr.StartRange().Ptr(),
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
package hcl
|
||||
|
||||
type unwrapExpression interface {
|
||||
UnwrapExpression() Expression
|
||||
}
|
||||
|
||||
// UnwrapExpression removes any "wrapper" expressions from the given expression,
|
||||
// to recover the representation of the physical expression given in source
|
||||
// code.
|
||||
//
|
||||
// Sometimes wrapping expressions are used to modify expression behavior, e.g.
|
||||
// in extensions that need to make some local variables available to certain
|
||||
// sub-trees of the configuration. This can make it difficult to reliably
|
||||
// type-assert on the physical AST types used by the underlying syntax.
|
||||
//
|
||||
// Unwrapping an expression may modify its behavior by stripping away any
|
||||
// additional constraints or capabilities being applied to the Value and
|
||||
// Variables methods, so this function should generally only be used prior
|
||||
// to operations that concern themselves with the static syntax of the input
|
||||
// configuration, and not with the effective value of the expression.
|
||||
//
|
||||
// Wrapper expression types must support unwrapping by implementing a method
|
||||
// called UnwrapExpression that takes no arguments and returns the embedded
|
||||
// Expression. Implementations of this method should peel away only one level
|
||||
// of wrapping, if multiple are present. This method may return nil to
|
||||
// indicate _dynamically_ that no wrapped expression is available, for
|
||||
// expression types that might only behave as wrappers in certain cases.
|
||||
func UnwrapExpression(expr Expression) Expression {
|
||||
for {
|
||||
unwrap, wrapped := expr.(unwrapExpression)
|
||||
if !wrapped {
|
||||
return expr
|
||||
}
|
||||
innerExpr := unwrap.UnwrapExpression()
|
||||
if innerExpr == nil {
|
||||
return expr
|
||||
}
|
||||
expr = innerExpr
|
||||
}
|
||||
}
|
||||
|
||||
// UnwrapExpressionUntil is similar to UnwrapExpression except it gives the
|
||||
// caller an opportunity to test each level of unwrapping to see each a
|
||||
// particular expression is accepted.
|
||||
//
|
||||
// This could be used, for example, to unwrap until a particular other
|
||||
// interface is satisfied, regardless of wrap wrapping level it is satisfied
|
||||
// at.
|
||||
//
|
||||
// The given callback function must return false to continue wrapping, or
|
||||
// true to accept and return the proposed expression given. If the callback
|
||||
// function rejects even the final, physical expression then the result of
|
||||
// this function is nil.
|
||||
func UnwrapExpressionUntil(expr Expression, until func(Expression) bool) Expression {
|
||||
for {
|
||||
if until(expr) {
|
||||
return expr
|
||||
}
|
||||
unwrap, wrapped := expr.(unwrapExpression)
|
||||
if !wrapped {
|
||||
return nil
|
||||
}
|
||||
expr = unwrap.UnwrapExpression()
|
||||
if expr == nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
// Package hclsyntax contains the parser, AST, etc for zcl's native language,
|
||||
// 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 hclparse.
|
||||
// its companion package hclparse.
|
||||
package hclsyntax
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
// Expression is the abstract type for nodes that behave as zcl expressions.
|
||||
// Expression is the abstract type for nodes that behave as HCL expressions.
|
||||
type Expression interface {
|
||||
Node
|
||||
|
||||
|
@ -70,6 +70,11 @@ func (e *ScopeTraversalExpr) StartRange() hcl.Range {
|
|||
return e.SrcRange
|
||||
}
|
||||
|
||||
// Implementation for hcl.AbsTraversalForExpr.
|
||||
func (e *ScopeTraversalExpr) AsTraversal() hcl.Traversal {
|
||||
return e.Traversal
|
||||
}
|
||||
|
||||
// RelativeTraversalExpr is an Expression that retrieves a value from another
|
||||
// value using a _relative_ traversal.
|
||||
type RelativeTraversalExpr struct {
|
||||
|
@ -539,6 +544,15 @@ func (e *TupleConsExpr) StartRange() hcl.Range {
|
|||
return e.OpenRange
|
||||
}
|
||||
|
||||
// Implementation for hcl.ExprList
|
||||
func (e *TupleConsExpr) ExprList() []hcl.Expression {
|
||||
ret := make([]hcl.Expression, len(e.Exprs))
|
||||
for i, expr := range e.Exprs {
|
||||
ret[i] = expr
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type ObjectConsExpr struct {
|
||||
Items []ObjectConsItem
|
||||
|
||||
|
|
|
@ -4,4 +4,6 @@ package hclsyntax
|
|||
//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
|
||||
|
|
|
@ -9,7 +9,7 @@ type navigation struct {
|
|||
root *Body
|
||||
}
|
||||
|
||||
// Implementation of zcled.ContextString
|
||||
// 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
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package hclsyntax
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/apparentlymart/go-textseg/textseg"
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
|
@ -627,7 +628,7 @@ Traversal:
|
|||
if lit, isLit := keyExpr.(*LiteralValueExpr); isLit {
|
||||
litKey, _ := lit.Value(nil)
|
||||
rng := hcl.RangeBetween(open.Range, close.Range)
|
||||
step := &hcl.TraverseIndex{
|
||||
step := hcl.TraverseIndex{
|
||||
Key: litKey,
|
||||
SrcRange: rng,
|
||||
}
|
||||
|
@ -1476,139 +1477,149 @@ func (p *parser) decodeStringLit(tok Token) (string, hcl.Diagnostics) {
|
|||
var diags hcl.Diagnostics
|
||||
|
||||
ret := make([]byte, 0, len(tok.Bytes))
|
||||
var esc []byte
|
||||
slices := scanStringLit(tok.Bytes, quoted)
|
||||
|
||||
sc := bufio.NewScanner(bytes.NewReader(tok.Bytes))
|
||||
sc.Split(textseg.ScanGraphemeClusters)
|
||||
// We will mutate rng constantly as we walk through our token slices below.
|
||||
// Any diagnostics must take a copy of this rng rather than simply pointing
|
||||
// to it, e.g. by using rng.Ptr() rather than &rng.
|
||||
rng := tok.Range
|
||||
rng.End = rng.Start
|
||||
|
||||
pos := tok.Range.Start
|
||||
newPos := pos
|
||||
Character:
|
||||
for sc.Scan() {
|
||||
pos = newPos
|
||||
ch := sc.Bytes()
|
||||
|
||||
// Adjust position based on our new character.
|
||||
// \r\n is considered to be a single character in text segmentation,
|
||||
if (len(ch) == 1 && ch[0] == '\n') || (len(ch) == 2 && ch[1] == '\n') {
|
||||
newPos.Line++
|
||||
newPos.Column = 0
|
||||
} else {
|
||||
newPos.Column++
|
||||
Slices:
|
||||
for _, slice := range slices {
|
||||
if len(slice) == 0 {
|
||||
continue
|
||||
}
|
||||
newPos.Byte += len(ch)
|
||||
|
||||
if len(esc) > 0 {
|
||||
switch esc[0] {
|
||||
case '\\':
|
||||
if len(ch) == 1 {
|
||||
switch ch[0] {
|
||||
// Advance the start of our range to where the previous token ended
|
||||
rng.Start = rng.End
|
||||
|
||||
// TODO: numeric character escapes with \uXXXX
|
||||
|
||||
case 'n':
|
||||
ret = append(ret, '\n')
|
||||
esc = esc[:0]
|
||||
continue Character
|
||||
case 'r':
|
||||
ret = append(ret, '\r')
|
||||
esc = esc[:0]
|
||||
continue Character
|
||||
case 't':
|
||||
ret = append(ret, '\t')
|
||||
esc = esc[:0]
|
||||
continue Character
|
||||
case '"':
|
||||
ret = append(ret, '"')
|
||||
esc = esc[:0]
|
||||
continue Character
|
||||
case '\\':
|
||||
ret = append(ret, '\\')
|
||||
esc = esc[:0]
|
||||
continue Character
|
||||
}
|
||||
}
|
||||
|
||||
var detail string
|
||||
switch {
|
||||
case len(ch) == 1 && (ch[0] == '$' || ch[0] == '!'):
|
||||
detail = fmt.Sprintf(
|
||||
"The characters \"\\%s\" do not form a recognized escape sequence. To escape a \"%s{\" template sequence, use \"%s%s{\".",
|
||||
ch, ch, ch, ch,
|
||||
)
|
||||
default:
|
||||
detail = fmt.Sprintf("The characters \"\\%s\" do not form a recognized escape sequence.", ch)
|
||||
}
|
||||
// Advance the end of our range to after our token.
|
||||
b := slice
|
||||
for len(b) > 0 {
|
||||
adv, ch, _ := textseg.ScanGraphemeClusters(b, true)
|
||||
rng.End.Byte += adv
|
||||
switch ch[0] {
|
||||
case '\r', '\n':
|
||||
rng.End.Line++
|
||||
rng.End.Column = 1
|
||||
default:
|
||||
rng.End.Column++
|
||||
}
|
||||
b = b[adv:]
|
||||
}
|
||||
|
||||
TokenType:
|
||||
switch slice[0] {
|
||||
case '\\':
|
||||
if !quoted {
|
||||
// If we're not in quoted mode then just treat this token as
|
||||
// normal. (Slices can still start with backslash even if we're
|
||||
// not specifically looking for backslash sequences.)
|
||||
break TokenType
|
||||
}
|
||||
if len(slice) < 2 {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid escape sequence",
|
||||
Detail: detail,
|
||||
Subject: &hcl.Range{
|
||||
Filename: tok.Range.Filename,
|
||||
Start: hcl.Pos{
|
||||
Line: pos.Line,
|
||||
Column: pos.Column - 1, // safe because we know the previous character must be a backslash
|
||||
Byte: pos.Byte - 1,
|
||||
},
|
||||
End: hcl.Pos{
|
||||
Line: pos.Line,
|
||||
Column: pos.Column + 1, // safe because we know the previous character must be a backslash
|
||||
Byte: pos.Byte + len(ch),
|
||||
},
|
||||
},
|
||||
Detail: "Backslash must be followed by an escape sequence selector character.",
|
||||
Subject: rng.Ptr(),
|
||||
})
|
||||
ret = append(ret, ch...)
|
||||
esc = esc[:0]
|
||||
continue Character
|
||||
break TokenType
|
||||
}
|
||||
|
||||
case '$', '!':
|
||||
switch len(esc) {
|
||||
case 1:
|
||||
if len(ch) == 1 && ch[0] == esc[0] {
|
||||
esc = append(esc, ch[0])
|
||||
continue Character
|
||||
}
|
||||
switch slice[1] {
|
||||
|
||||
// Any other character means this wasn't an escape sequence
|
||||
// after all.
|
||||
ret = append(ret, esc...)
|
||||
ret = append(ret, ch...)
|
||||
esc = esc[:0]
|
||||
case 2:
|
||||
if len(ch) == 1 && ch[0] == '{' {
|
||||
// successful escape sequence
|
||||
ret = append(ret, esc[0])
|
||||
} else {
|
||||
// not an escape sequence, so just output literal
|
||||
ret = append(ret, esc...)
|
||||
}
|
||||
ret = append(ret, ch...)
|
||||
esc = esc[:0]
|
||||
default:
|
||||
// should never happen
|
||||
panic("have invalid escape sequence >2 characters")
|
||||
case 'n':
|
||||
ret = append(ret, '\n')
|
||||
continue Slices
|
||||
case 'r':
|
||||
ret = append(ret, '\r')
|
||||
continue Slices
|
||||
case 't':
|
||||
ret = append(ret, '\t')
|
||||
continue Slices
|
||||
case '"':
|
||||
ret = append(ret, '"')
|
||||
continue Slices
|
||||
case '\\':
|
||||
ret = append(ret, '\\')
|
||||
continue Slices
|
||||
case 'u', 'U':
|
||||
if slice[1] == 'u' && len(slice) != 6 {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid escape sequence",
|
||||
Detail: "The \\u escape sequence must be followed by four hexadecimal digits.",
|
||||
Subject: rng.Ptr(),
|
||||
})
|
||||
break TokenType
|
||||
} else if slice[1] == 'U' && len(slice) != 10 {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid escape sequence",
|
||||
Detail: "The \\U escape sequence must be followed by eight hexadecimal digits.",
|
||||
Subject: rng.Ptr(),
|
||||
})
|
||||
break TokenType
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
if len(ch) == 1 {
|
||||
switch ch[0] {
|
||||
case '\\':
|
||||
if quoted { // ignore backslashes in unquoted mode
|
||||
esc = append(esc, '\\')
|
||||
continue Character
|
||||
}
|
||||
case '$':
|
||||
esc = append(esc, '$')
|
||||
continue Character
|
||||
case '!':
|
||||
esc = append(esc, '!')
|
||||
continue Character
|
||||
numHex := string(slice[2:])
|
||||
num, err := strconv.ParseUint(numHex, 16, 32)
|
||||
if err != nil {
|
||||
// Should never happen because the scanner won't match
|
||||
// a sequence of digits that isn't valid.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
r := rune(num)
|
||||
l := utf8.RuneLen(r)
|
||||
if l == -1 {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid escape sequence",
|
||||
Detail: fmt.Sprintf("Cannot encode character U+%04x in UTF-8.", num),
|
||||
Subject: rng.Ptr(),
|
||||
})
|
||||
break TokenType
|
||||
}
|
||||
for i := 0; i < l; i++ {
|
||||
ret = append(ret, 0)
|
||||
}
|
||||
rb := ret[len(ret)-l:]
|
||||
utf8.EncodeRune(rb, r)
|
||||
|
||||
continue Slices
|
||||
|
||||
default:
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid escape sequence",
|
||||
Detail: fmt.Sprintf("The symbol %q is not a valid escape sequence selector.", slice[1:]),
|
||||
Subject: rng.Ptr(),
|
||||
})
|
||||
ret = append(ret, slice[1:]...)
|
||||
continue Slices
|
||||
}
|
||||
ret = append(ret, ch...)
|
||||
|
||||
case '$', '%':
|
||||
if len(slice) != 3 {
|
||||
// Not long enough to be our escape sequence, so it's literal.
|
||||
break TokenType
|
||||
}
|
||||
|
||||
if slice[1] == slice[0] && slice[2] == '{' {
|
||||
ret = append(ret, slice[0])
|
||||
ret = append(ret, '{')
|
||||
continue Slices
|
||||
}
|
||||
|
||||
break TokenType
|
||||
}
|
||||
|
||||
// If we fall out here or break out of here from the switch above
|
||||
// then this slice is just a literal.
|
||||
ret = append(ret, slice...)
|
||||
}
|
||||
|
||||
return string(ret), diags
|
||||
|
|
|
@ -435,7 +435,7 @@ Token:
|
|||
})
|
||||
|
||||
case TokenTemplateControl:
|
||||
// if the opener is !{~ then we want to eat any trailing whitespace
|
||||
// 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 {
|
||||
|
@ -452,7 +452,7 @@ Token:
|
|||
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.",
|
||||
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(),
|
||||
})
|
||||
|
|
|
@ -4,13 +4,13 @@ import (
|
|||
"github.com/hashicorp/hcl2/hcl"
|
||||
)
|
||||
|
||||
// ParseConfig parses the given buffer as a whole zcl config file, returning
|
||||
// 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 *zclsyntax.Body, so callers
|
||||
// may freely type-assert this to get access to the full zclsyntax API in
|
||||
// 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.
|
||||
|
@ -30,7 +30,7 @@ func ParseConfig(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Dia
|
|||
}, diags
|
||||
}
|
||||
|
||||
// ParseExpression parses the given buffer as a standalone zcl expression,
|
||||
// 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)
|
||||
|
@ -57,7 +57,7 @@ func ParseExpression(src []byte, filename string, start hcl.Pos) (Expression, hc
|
|||
return expr, diags
|
||||
}
|
||||
|
||||
// ParseTemplate parses the given buffer as a standalone zcl template,
|
||||
// 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)
|
||||
|
@ -89,7 +89,7 @@ func ParseTraversalAbs(src []byte, filename string, start hcl.Pos) (hcl.Traversa
|
|||
}
|
||||
|
||||
// LexConfig performs lexical analysis on the given buffer, treating it as a
|
||||
// whole zcl config file, and returns the resulting tokens.
|
||||
// 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
|
||||
|
@ -102,7 +102,7 @@ func LexConfig(src []byte, filename string, start hcl.Pos) (Tokens, hcl.Diagnost
|
|||
}
|
||||
|
||||
// LexExpression performs lexical analysis on the given buffer, treating it as
|
||||
// a standalone zcl expression, and returns the resulting tokens.
|
||||
// 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
|
||||
|
@ -117,7 +117,7 @@ func LexExpression(src []byte, filename string, start hcl.Pos) (Tokens, hcl.Diag
|
|||
}
|
||||
|
||||
// LexTemplate performs lexical analysis on the given buffer, treating it as a
|
||||
// standalone zcl template, and returns the resulting tokens.
|
||||
// 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
|
||||
|
@ -128,3 +128,17 @@ func LexTemplate(src []byte, filename string, start hcl.Pos) (Tokens, hcl.Diagno
|
|||
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/hcl2/hcl/hclsyntax/scan_string_lit.go
generated
vendored
Normal file
301
vendor/github.com/hashicorp/hcl2/hcl/hclsyntax/scan_string_lit.go
generated
vendored
Normal file
|
@ -0,0 +1,301 @@
|
|||
// line 1 "scan_string_lit.rl"
|
||||
|
||||
package hclsyntax
|
||||
|
||||
// This file is generated from scan_string_lit.rl. DO NOT EDIT.
|
||||
|
||||
// line 9 "scan_string_lit.go"
|
||||
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 10 "scan_string_lit.rl"
|
||||
|
||||
func scanStringLit(data []byte, quoted bool) [][]byte {
|
||||
var ret [][]byte
|
||||
|
||||
// line 61 "scan_string_lit.rl"
|
||||
|
||||
// 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 154 "scan_string_lit.go"
|
||||
{
|
||||
}
|
||||
|
||||
// line 158 "scan_string_lit.go"
|
||||
{
|
||||
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 40 "scan_string_lit.rl"
|
||||
|
||||
// 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 48 "scan_string_lit.rl"
|
||||
|
||||
te = p
|
||||
ret = append(ret, data[ts:te])
|
||||
|
||||
// line 255 "scan_string_lit.go"
|
||||
}
|
||||
}
|
||||
|
||||
_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 48 "scan_string_lit.rl"
|
||||
|
||||
te = p
|
||||
ret = append(ret, data[ts:te])
|
||||
|
||||
// line 281 "scan_string_lit.go"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_out:
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// line 89 "scan_string_lit.rl"
|
||||
|
||||
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/hcl2/hcl/hclsyntax/scan_string_lit.rl
generated
vendored
Normal file
105
vendor/github.com/hashicorp/hcl2/hcl/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
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,16 +1,17 @@
|
|||
package zclsyntax
|
||||
|
||||
package hclsyntax
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/zclconf/go-zcl/zcl"
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
)
|
||||
|
||||
// This file is generated from scan_tokens.rl. DO NOT EDIT.
|
||||
%%{
|
||||
# (except you are actually in scan_tokens.rl here, so edit away!)
|
||||
|
||||
machine zcltok;
|
||||
machine hcltok;
|
||||
write data;
|
||||
}%%
|
||||
|
||||
|
@ -62,15 +63,13 @@ func scanTokens(data []byte, filename string, start hcl.Pos, mode scanMode) []To
|
|||
("/*" any* "*/")
|
||||
);
|
||||
|
||||
# Tabs are not valid, but we accept them in the scanner and mark them
|
||||
# as tokens so that we can produce diagnostics advising the user to
|
||||
# use spaces instead.
|
||||
Tabs = 0x09+;
|
||||
|
||||
# Note: zclwrite assumes that only ASCII spaces appear between tokens,
|
||||
# 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.
|
||||
Spaces = ' '+;
|
||||
# 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);
|
||||
|
@ -238,6 +237,12 @@ func scanTokens(data []byte, filename string, start hcl.Pos, mode scanMode) []To
|
|||
BrokenUTF8 => { token(TokenBadUTF8); };
|
||||
*|;
|
||||
|
||||
identOnly := |*
|
||||
Ident => { token(TokenIdent) };
|
||||
BrokenUTF8 => { token(TokenBadUTF8) };
|
||||
AnyUTF8 => { token(TokenInvalid) };
|
||||
*|;
|
||||
|
||||
main := |*
|
||||
Spaces => {};
|
||||
NumberLit => { token(TokenNumberLit) };
|
||||
|
@ -264,7 +269,6 @@ func scanTokens(data []byte, filename string, start hcl.Pos, mode scanMode) []To
|
|||
BeginStringTmpl => beginStringTemplate;
|
||||
BeginHeredocTmpl => beginHeredocTemplate;
|
||||
|
||||
Tabs => { token(TokenTabs) };
|
||||
BrokenUTF8 => { token(TokenBadUTF8) };
|
||||
AnyUTF8 => { token(TokenInvalid) };
|
||||
*|;
|
||||
|
@ -284,9 +288,11 @@ func scanTokens(data []byte, filename string, start hcl.Pos, mode scanMode) []To
|
|||
var cs int // current state
|
||||
switch mode {
|
||||
case scanNormal:
|
||||
cs = zcltok_en_main
|
||||
cs = hcltok_en_main
|
||||
case scanTemplate:
|
||||
cs = zcltok_en_bareTemplate
|
||||
cs = hcltok_en_bareTemplate
|
||||
case scanIdentOnly:
|
||||
cs = hcltok_en_identOnly
|
||||
default:
|
||||
panic("invalid scanMode")
|
||||
}
|
||||
|
@ -330,7 +336,7 @@ func scanTokens(data []byte, filename string, start hcl.Pos, mode scanMode) []To
|
|||
// 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 < zcltok_first_final {
|
||||
if cs < hcltok_first_final {
|
||||
f.emitToken(TokenInvalid, p, len(data))
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ func (b *Block) AsHCLBlock() *hcl.Block {
|
|||
}
|
||||
}
|
||||
|
||||
// Body is the implementation of hcl.Body for the zcl native syntax.
|
||||
// Body is the implementation of hcl.Body for the HCL native syntax.
|
||||
type Body struct {
|
||||
Attributes Attributes
|
||||
Blocks Blocks
|
||||
|
|
|
@ -110,6 +110,7 @@ type scanMode int
|
|||
const (
|
||||
scanNormal scanMode = iota
|
||||
scanTemplate
|
||||
scanIdentOnly
|
||||
)
|
||||
|
||||
type tokenAccum struct {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
package hclsyntax
|
||||
|
||||
import "fmt"
|
||||
import "strconv"
|
||||
|
||||
const _TokenType_name = "TokenNilTokenNewlineTokenBangTokenPercentTokenBitwiseAndTokenOParenTokenCParenTokenStarTokenPlusTokenCommaTokenMinusTokenDotTokenSlashTokenColonTokenSemicolonTokenLessThanTokenEqualTokenGreaterThanTokenQuestionTokenCommentTokenOHeredocTokenIdentTokenNumberLitTokenQuotedLitTokenStringLitTokenOBrackTokenCBrackTokenBitwiseXorTokenBacktickTokenCHeredocTokenOBraceTokenBitwiseOrTokenCBraceTokenBitwiseNotTokenOQuoteTokenCQuoteTokenTemplateControlTokenEllipsisTokenFatArrowTokenTemplateSeqEndTokenAndTokenOrTokenTemplateInterpTokenEqualOpTokenNotEqualTokenLessThanEqTokenGreaterThanEqTokenEOFTokenTabsTokenStarStarTokenInvalidTokenBadUTF8"
|
||||
|
||||
|
@ -65,5 +65,5 @@ func (i TokenType) String() string {
|
|||
if str, ok := _TokenType_map[i]; ok {
|
||||
return str
|
||||
}
|
||||
return fmt.Sprintf("TokenType(%d)", i)
|
||||
return "TokenType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ type navigation struct {
|
|||
root *objectVal
|
||||
}
|
||||
|
||||
// Implementation of zcled.ContextString
|
||||
// Implementation of hcled.ContextString
|
||||
func (n navigation) ContextString(offset int) string {
|
||||
steps := navigationStepsRev(n.root, offset)
|
||||
if steps == nil {
|
||||
|
|
|
@ -168,7 +168,7 @@ Token:
|
|||
}
|
||||
|
||||
if colon.Type == tokenEquals {
|
||||
// Possible confusion with native zcl syntax.
|
||||
// Possible confusion with native HCL syntax.
|
||||
return nil, diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing attribute value colon",
|
||||
|
|
|
@ -9,10 +9,10 @@ import (
|
|||
)
|
||||
|
||||
// Parse attempts to parse the given buffer as JSON and, if successful, returns
|
||||
// a hcl.File for the zcl configuration represented by it.
|
||||
// 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 zcl configuration.
|
||||
// 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
|
||||
|
@ -88,28 +88,3 @@ func ParseFile(filename string) (*hcl.File, hcl.Diagnostics) {
|
|||
|
||||
return Parse(src, filename)
|
||||
}
|
||||
|
||||
// ParseWithHIL is like Parse except the returned file will use the HIL
|
||||
// template syntax for expressions in strings, rather than the native zcl
|
||||
// template syntax.
|
||||
//
|
||||
// This is intended for providing backward compatibility for applications that
|
||||
// used to use HCL/HIL and thus had a JSON-based format with HIL
|
||||
// interpolations.
|
||||
func ParseWithHIL(src []byte, filename string) (*hcl.File, hcl.Diagnostics) {
|
||||
file, diags := Parse(src, filename)
|
||||
if file != nil && file.Body != nil {
|
||||
file.Body.(*body).useHIL = true
|
||||
}
|
||||
return file, diags
|
||||
}
|
||||
|
||||
// ParseFileWithHIL is like ParseWithHIL but it reads data from a file before
|
||||
// parsing it.
|
||||
func ParseFileWithHIL(filename string) (*hcl.File, hcl.Diagnostics) {
|
||||
file, diags := ParseFile(filename)
|
||||
if file != nil && file.Body != nil {
|
||||
file.Body.(*body).useHIL = true
|
||||
}
|
||||
return file, diags
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
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](../zclsyntax/spec.md)
|
||||
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 maintenence.
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ package json
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
|
@ -17,12 +17,6 @@ type body struct {
|
|||
// be treated as non-existing. This is used when Body.PartialContent is
|
||||
// called, to produce the "remaining content" Body.
|
||||
hiddenAttrs map[string]struct{}
|
||||
|
||||
// If set, string values are turned into expressions using HIL's template
|
||||
// language, rather than the native zcl language. This is intended to
|
||||
// allow applications moving from HCL to zcl to continue to parse the
|
||||
// JSON variant of their config that HCL handled previously.
|
||||
useHIL bool
|
||||
}
|
||||
|
||||
// expression is the implementation of "Expression" used for files processed
|
||||
|
@ -133,7 +127,6 @@ func (b *body) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Bod
|
|||
unusedBody := &body{
|
||||
obj: b.obj,
|
||||
hiddenAttrs: usedNames,
|
||||
useHIL: b.useHIL,
|
||||
}
|
||||
|
||||
return content, unusedBody, diags
|
||||
|
@ -219,8 +212,7 @@ func (b *body) unpackBlock(v node, typeName string, typeRange *hcl.Range, labels
|
|||
Type: typeName,
|
||||
Labels: labels,
|
||||
Body: &body{
|
||||
obj: tv,
|
||||
useHIL: b.useHIL,
|
||||
obj: tv,
|
||||
},
|
||||
|
||||
DefRange: tv.OpenRange,
|
||||
|
@ -245,8 +237,7 @@ func (b *body) unpackBlock(v node, typeName string, typeRange *hcl.Range, labels
|
|||
Type: typeName,
|
||||
Labels: labels,
|
||||
Body: &body{
|
||||
obj: ov,
|
||||
useHIL: b.useHIL,
|
||||
obj: ov,
|
||||
},
|
||||
|
||||
DefRange: tv.OpenRange,
|
||||
|
@ -269,7 +260,7 @@ 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 zcl native language expression.
|
||||
// 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.
|
||||
|
@ -279,7 +270,7 @@ func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|||
v.SrcRange.Filename,
|
||||
|
||||
// This won't produce _exactly_ the right result, since
|
||||
// the zclsyntax parser can't "see" any escapes we removed
|
||||
// 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,
|
||||
|
@ -297,8 +288,6 @@ func (e *expression) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
|||
return val, diags
|
||||
}
|
||||
|
||||
// FIXME: Once the native zcl template language parser is implemented,
|
||||
// parse string values as templates and evaluate them.
|
||||
return cty.StringVal(v.Value), nil
|
||||
case *numberVal:
|
||||
return cty.NumberVal(v.Value), nil
|
||||
|
@ -330,8 +319,26 @@ func (e *expression) Variables() []hcl.Traversal {
|
|||
|
||||
switch v := e.src.(type) {
|
||||
case *stringVal:
|
||||
// FIXME: Once the native zcl template language parser is implemented,
|
||||
// parse with that and look for variables in there too,
|
||||
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 {
|
||||
|
@ -353,3 +360,34 @@ func (e *expression) Range() hcl.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.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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
)
|
||||
|
||||
// Index is a helper function that performs the same operation as the index
|
||||
// operator in the zcl expression language. That is, the result is the
|
||||
// operator in the HCL expression language. That is, the result is the
|
||||
// same as it would be for collection[key] in a configuration expression.
|
||||
//
|
||||
// This is exported so that applications can perform indexing in a manner
|
||||
|
|
|
@ -60,6 +60,40 @@ func RangeBetween(start, end Range) Range {
|
|||
}
|
||||
}
|
||||
|
||||
// RangeOver returns a new range that covers both of the given ranges and
|
||||
// possibly additional content between them if the two ranges do not overlap.
|
||||
//
|
||||
// If either range is empty then it is ignored. The result is empty if both
|
||||
// given ranges are empty.
|
||||
//
|
||||
// The result is meaningless if the two ranges to not belong to the same
|
||||
// source file.
|
||||
func RangeOver(a, b Range) Range {
|
||||
if a.Empty() {
|
||||
return b
|
||||
}
|
||||
if b.Empty() {
|
||||
return a
|
||||
}
|
||||
|
||||
var start, end Pos
|
||||
if a.Start.Byte < b.Start.Byte {
|
||||
start = a.Start
|
||||
} else {
|
||||
start = b.Start
|
||||
}
|
||||
if a.End.Byte > b.End.Byte {
|
||||
end = a.End
|
||||
} else {
|
||||
end = b.End
|
||||
}
|
||||
return Range{
|
||||
Filename: a.Filename,
|
||||
Start: start,
|
||||
End: end,
|
||||
}
|
||||
}
|
||||
|
||||
// ContainsOffset returns true if and only if the given byte offset is within
|
||||
// the receiving Range.
|
||||
func (r Range) ContainsOffset(offset int) bool {
|
||||
|
@ -94,3 +128,135 @@ func (r Range) String() string {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (r Range) Empty() bool {
|
||||
return r.Start.Byte == r.End.Byte
|
||||
}
|
||||
|
||||
// CanSliceBytes returns true if SliceBytes could return an accurate
|
||||
// sub-slice of the given slice.
|
||||
//
|
||||
// This effectively tests whether the start and end offsets of the range
|
||||
// are within the bounds of the slice, and thus whether SliceBytes can be
|
||||
// trusted to produce an accurate start and end position within that slice.
|
||||
func (r Range) CanSliceBytes(b []byte) bool {
|
||||
switch {
|
||||
case r.Start.Byte < 0 || r.Start.Byte > len(b):
|
||||
return false
|
||||
case r.End.Byte < 0 || r.End.Byte > len(b):
|
||||
return false
|
||||
case r.End.Byte < r.Start.Byte:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// SliceBytes returns a sub-slice of the given slice that is covered by the
|
||||
// receiving range, assuming that the given slice is the source code of the
|
||||
// file indicated by r.Filename.
|
||||
//
|
||||
// If the receiver refers to any byte offsets that are outside of the slice
|
||||
// then the result is constrained to the overlapping portion only, to avoid
|
||||
// a panic. Use CanSliceBytes to determine if the result is guaranteed to
|
||||
// be an accurate span of the requested range.
|
||||
func (r Range) SliceBytes(b []byte) []byte {
|
||||
start := r.Start.Byte
|
||||
end := r.End.Byte
|
||||
if start < 0 {
|
||||
start = 0
|
||||
} else if start > len(b) {
|
||||
start = len(b)
|
||||
}
|
||||
if end < 0 {
|
||||
end = 0
|
||||
} else if end > len(b) {
|
||||
end = len(b)
|
||||
}
|
||||
if end < start {
|
||||
end = start
|
||||
}
|
||||
return b[start:end]
|
||||
}
|
||||
|
||||
// Overlaps returns true if the receiver and the other given range share any
|
||||
// characters in common.
|
||||
func (r Range) Overlaps(other Range) bool {
|
||||
switch {
|
||||
case r.Filename != other.Filename:
|
||||
// If the ranges are in different files then they can't possibly overlap
|
||||
return false
|
||||
case r.Empty() || other.Empty():
|
||||
// Empty ranges can never overlap
|
||||
return false
|
||||
case r.ContainsOffset(other.Start.Byte) || r.ContainsOffset(other.End.Byte):
|
||||
return true
|
||||
case other.ContainsOffset(r.Start.Byte) || other.ContainsOffset(r.End.Byte):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Overlap finds a range that is either identical to or a sub-range of both
|
||||
// the receiver and the other given range. It returns an empty range
|
||||
// within the receiver if there is no overlap between the two ranges.
|
||||
//
|
||||
// A non-empty result is either identical to or a subset of the receiver.
|
||||
func (r Range) Overlap(other Range) Range {
|
||||
if !r.Overlaps(other) {
|
||||
// Start == End indicates an empty range
|
||||
return Range{
|
||||
Filename: r.Filename,
|
||||
Start: r.Start,
|
||||
End: r.Start,
|
||||
}
|
||||
}
|
||||
|
||||
var start, end Pos
|
||||
if r.Start.Byte > other.Start.Byte {
|
||||
start = r.Start
|
||||
} else {
|
||||
start = other.Start
|
||||
}
|
||||
if r.End.Byte < other.End.Byte {
|
||||
end = r.End
|
||||
} else {
|
||||
end = other.End
|
||||
}
|
||||
|
||||
return Range{
|
||||
Filename: r.Filename,
|
||||
Start: start,
|
||||
End: end,
|
||||
}
|
||||
}
|
||||
|
||||
// PartitionAround finds the portion of the given range that overlaps with
|
||||
// the reciever and returns three ranges: the portion of the reciever that
|
||||
// precedes the overlap, the overlap itself, and then the portion of the
|
||||
// reciever that comes after the overlap.
|
||||
//
|
||||
// If the two ranges do not overlap then all three returned ranges are empty.
|
||||
//
|
||||
// If the given range aligns with or extends beyond either extent of the
|
||||
// reciever then the corresponding outer range will be empty.
|
||||
func (r Range) PartitionAround(other Range) (before, overlap, after Range) {
|
||||
overlap = r.Overlap(other)
|
||||
if overlap.Empty() {
|
||||
return overlap, overlap, overlap
|
||||
}
|
||||
|
||||
before = Range{
|
||||
Filename: r.Filename,
|
||||
Start: r.Start,
|
||||
End: overlap.Start,
|
||||
}
|
||||
after = Range{
|
||||
Filename: r.Filename,
|
||||
Start: overlap.End,
|
||||
End: r.End,
|
||||
}
|
||||
|
||||
return before, overlap, after
|
||||
}
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
package hcl
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
|
||||
"github.com/apparentlymart/go-textseg/textseg"
|
||||
)
|
||||
|
||||
// RangeScanner is a helper that will scan over a buffer using a bufio.SplitFunc
|
||||
// and visit a source range for each token matched.
|
||||
//
|
||||
// For example, this can be used with bufio.ScanLines to find the source range
|
||||
// for each line in the file, skipping over the actual newline characters, which
|
||||
// may be useful when printing source code snippets as part of diagnostic
|
||||
// messages.
|
||||
//
|
||||
// The line and column information in the returned ranges is produced by
|
||||
// counting newline characters and grapheme clusters respectively, which
|
||||
// mimics the behavior we expect from a parser when producing ranges.
|
||||
type RangeScanner struct {
|
||||
filename string
|
||||
b []byte
|
||||
cb bufio.SplitFunc
|
||||
|
||||
pos Pos // position of next byte to process in b
|
||||
cur Range // latest range
|
||||
tok []byte // slice of b that is covered by cur
|
||||
err error // error from last scan, if any
|
||||
}
|
||||
|
||||
// Create a new RangeScanner for the given buffer, producing ranges for the
|
||||
// given filename.
|
||||
//
|
||||
// Since ranges have grapheme-cluster granularity rather than byte granularity,
|
||||
// the scanner will produce incorrect results if the given SplitFunc creates
|
||||
// tokens between grapheme cluster boundaries. In particular, it is incorrect
|
||||
// to use RangeScanner with bufio.ScanRunes because it will produce tokens
|
||||
// around individual UTF-8 sequences, which will split any multi-sequence
|
||||
// grapheme clusters.
|
||||
func NewRangeScanner(b []byte, filename string, cb bufio.SplitFunc) *RangeScanner {
|
||||
return &RangeScanner{
|
||||
filename: filename,
|
||||
b: b,
|
||||
cb: cb,
|
||||
pos: Pos{
|
||||
Byte: 0,
|
||||
Line: 1,
|
||||
Column: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (sc *RangeScanner) Scan() bool {
|
||||
if sc.pos.Byte >= len(sc.b) || sc.err != nil {
|
||||
// All done
|
||||
return false
|
||||
}
|
||||
|
||||
// Since we're operating on an in-memory buffer, we always pass the whole
|
||||
// remainder of the buffer to our SplitFunc and set isEOF to let it know
|
||||
// that it has the whole thing.
|
||||
advance, token, err := sc.cb(sc.b[sc.pos.Byte:], true)
|
||||
|
||||
// Since we are setting isEOF to true this should never happen, but
|
||||
// if it does we will just abort and assume the SplitFunc is misbehaving.
|
||||
if advance == 0 && token == nil && err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
sc.err = err
|
||||
sc.cur = Range{
|
||||
Filename: sc.filename,
|
||||
Start: sc.pos,
|
||||
End: sc.pos,
|
||||
}
|
||||
sc.tok = nil
|
||||
return false
|
||||
}
|
||||
|
||||
sc.tok = token
|
||||
start := sc.pos
|
||||
end := sc.pos
|
||||
new := sc.pos
|
||||
|
||||
// adv is similar to token but it also includes any subsequent characters
|
||||
// we're being asked to skip over by the SplitFunc.
|
||||
// adv is a slice covering any additional bytes we are skipping over, based
|
||||
// on what the SplitFunc told us to do with advance.
|
||||
adv := sc.b[sc.pos.Byte : sc.pos.Byte+advance]
|
||||
|
||||
// We now need to scan over our token to count the grapheme clusters
|
||||
// so we can correctly advance Column, and count the newlines so we
|
||||
// can correctly advance Line.
|
||||
advR := bytes.NewReader(adv)
|
||||
gsc := bufio.NewScanner(advR)
|
||||
advanced := 0
|
||||
gsc.Split(textseg.ScanGraphemeClusters)
|
||||
for gsc.Scan() {
|
||||
gr := gsc.Bytes()
|
||||
new.Byte += len(gr)
|
||||
new.Column++
|
||||
|
||||
// We rely here on the fact that \r\n is considered a grapheme cluster
|
||||
// and so we don't need to worry about miscounting additional lines
|
||||
// on files with Windows-style line endings.
|
||||
if len(gr) != 0 && (gr[0] == '\r' || gr[0] == '\n') {
|
||||
new.Column = 1
|
||||
new.Line++
|
||||
}
|
||||
|
||||
if advanced < len(token) {
|
||||
// If we've not yet found the end of our token then we'll
|
||||
// also push our "end" marker along.
|
||||
// (if advance > len(token) then we'll stop moving "end" early
|
||||
// so that the caller only sees the range covered by token.)
|
||||
end = new
|
||||
}
|
||||
advanced += len(gr)
|
||||
}
|
||||
|
||||
sc.cur = Range{
|
||||
Filename: sc.filename,
|
||||
Start: start,
|
||||
End: end,
|
||||
}
|
||||
sc.pos = new
|
||||
return true
|
||||
}
|
||||
|
||||
// Range returns a range that covers the latest token obtained after a call
|
||||
// to Scan returns true.
|
||||
func (sc *RangeScanner) Range() Range {
|
||||
return sc.cur
|
||||
}
|
||||
|
||||
// Bytes returns the slice of the input buffer that is covered by the range
|
||||
// that would be returned by Range.
|
||||
func (sc *RangeScanner) Bytes() []byte {
|
||||
return sc.tok
|
||||
}
|
||||
|
||||
// Err can be called after Scan returns false to determine if the latest read
|
||||
// resulted in an error, and obtain that error if so.
|
||||
func (sc *RangeScanner) Err() error {
|
||||
return sc.err
|
||||
}
|
|
@ -7,7 +7,7 @@ concrete syntaxes for configuration, each with a mapping to the model defined
|
|||
in this specification.
|
||||
|
||||
The two primary syntaxes intended for use in conjunction with this model are
|
||||
[the HCL native syntax](./zclsyntax/spec.md) and [the JSON syntax](./json/spec.md).
|
||||
[the HCL native syntax](./hclsyntax/spec.md) and [the JSON syntax](./json/spec.md).
|
||||
In principle other syntaxes are possible as long as either their language model
|
||||
is sufficiently rich to express the concepts described in this specification
|
||||
or the language targets a well-defined subset of the specification.
|
||||
|
@ -159,7 +159,7 @@ a computation in terms of literal values, variables, and functions.
|
|||
Each syntax defines its own representation of expressions. For syntaxes based
|
||||
in languages that do not have any non-literal expression syntax, it is
|
||||
recommended to embed the template language from
|
||||
[the native syntax](./zclsyntax/spec.md) e.g. as a post-processing step on
|
||||
[the native syntax](./hclsyntax/spec.md) e.g. as a post-processing step on
|
||||
string literals.
|
||||
|
||||
### Expression Evaluation
|
||||
|
|
|
@ -156,6 +156,17 @@ func (t Traversal) RootName() string {
|
|||
return t[0].(TraverseRoot).Name
|
||||
}
|
||||
|
||||
// SourceRange returns the source range for the traversal.
|
||||
func (t Traversal) SourceRange() Range {
|
||||
if len(t) == 0 {
|
||||
// Nothing useful to return here, but we'll return something
|
||||
// that's correctly-typed at least.
|
||||
return Range{}
|
||||
}
|
||||
|
||||
return RangeBetween(t[0].SourceRange(), t[len(t)-1].SourceRange())
|
||||
}
|
||||
|
||||
// TraversalSplit represents a pair of traversals, the first of which is
|
||||
// an absolute traversal and the second of which is relative to the first.
|
||||
//
|
||||
|
@ -206,6 +217,7 @@ func (t TraversalSplit) RootName() string {
|
|||
// A Traverser is a step within a Traversal.
|
||||
type Traverser interface {
|
||||
TraversalStep(cty.Value) (cty.Value, Diagnostics)
|
||||
SourceRange() Range
|
||||
isTraverserSigil() isTraverser
|
||||
}
|
||||
|
||||
|
@ -231,6 +243,10 @@ func (tn TraverseRoot) TraversalStep(cty.Value) (cty.Value, Diagnostics) {
|
|||
panic("Cannot traverse an absolute traversal")
|
||||
}
|
||||
|
||||
func (tn TraverseRoot) SourceRange() Range {
|
||||
return tn.SrcRange
|
||||
}
|
||||
|
||||
// TraverseAttr looks up an attribute in its initial value.
|
||||
type TraverseAttr struct {
|
||||
isTraverser
|
||||
|
@ -301,6 +317,10 @@ func (tn TraverseAttr) TraversalStep(val cty.Value) (cty.Value, Diagnostics) {
|
|||
}
|
||||
}
|
||||
|
||||
func (tn TraverseAttr) SourceRange() Range {
|
||||
return tn.SrcRange
|
||||
}
|
||||
|
||||
// TraverseIndex applies the index operation to its initial value.
|
||||
type TraverseIndex struct {
|
||||
isTraverser
|
||||
|
@ -312,6 +332,10 @@ func (tn TraverseIndex) TraversalStep(val cty.Value) (cty.Value, Diagnostics) {
|
|||
return Index(val, tn.Key, &tn.SrcRange)
|
||||
}
|
||||
|
||||
func (tn TraverseIndex) SourceRange() Range {
|
||||
return tn.SrcRange
|
||||
}
|
||||
|
||||
// TraverseSplat applies the splat operation to its initial value.
|
||||
type TraverseSplat struct {
|
||||
isTraverser
|
||||
|
@ -322,3 +346,7 @@ type TraverseSplat struct {
|
|||
func (tn TraverseSplat) TraversalStep(val cty.Value) (cty.Value, Diagnostics) {
|
||||
panic("TraverseSplat not yet implemented")
|
||||
}
|
||||
|
||||
func (tn TraverseSplat) SourceRange() Range {
|
||||
return tn.SrcRange
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
package hcl
|
||||
|
||||
// AbsTraversalForExpr attempts to interpret the given expression as
|
||||
// an absolute traversal, or returns error diagnostic(s) if that is
|
||||
// not possible for the given expression.
|
||||
//
|
||||
// A particular Expression implementation can support this function by
|
||||
// offering a method called AsTraversal that takes no arguments and
|
||||
// returns either a valid absolute traversal or nil to indicate that
|
||||
// no traversal is possible. Alternatively, an implementation can support
|
||||
// UnwrapExpression to delegate handling of this function to a wrapped
|
||||
// Expression object.
|
||||
//
|
||||
// In most cases the calling application is interested in the value
|
||||
// that results from an expression, but in rarer cases the application
|
||||
// needs to see the the name of the variable and subsequent
|
||||
// attributes/indexes itself, for example to allow users to give references
|
||||
// to the variables themselves rather than to their values. An implementer
|
||||
// of this function should at least support attribute and index steps.
|
||||
func AbsTraversalForExpr(expr Expression) (Traversal, Diagnostics) {
|
||||
type asTraversal interface {
|
||||
AsTraversal() Traversal
|
||||
}
|
||||
|
||||
physExpr := UnwrapExpressionUntil(expr, func(expr Expression) bool {
|
||||
_, supported := expr.(asTraversal)
|
||||
return supported
|
||||
})
|
||||
|
||||
if asT, supported := physExpr.(asTraversal); supported {
|
||||
if traversal := asT.AsTraversal(); traversal != nil {
|
||||
return traversal, nil
|
||||
}
|
||||
}
|
||||
return nil, Diagnostics{
|
||||
&Diagnostic{
|
||||
Severity: DiagError,
|
||||
Summary: "Invalid expression",
|
||||
Detail: "A static variable reference is required.",
|
||||
Subject: expr.Range().Ptr(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// RelTraversalForExpr is similar to AbsTraversalForExpr but it returns
|
||||
// a relative traversal instead. Due to the nature of HCL expressions, the
|
||||
// first element of the returned traversal is always a TraverseAttr, and
|
||||
// then it will be followed by zero or more other expressions.
|
||||
//
|
||||
// Any expression accepted by AbsTraversalForExpr is also accepted by
|
||||
// RelTraversalForExpr.
|
||||
func RelTraversalForExpr(expr Expression) (Traversal, Diagnostics) {
|
||||
traversal, diags := AbsTraversalForExpr(expr)
|
||||
if len(traversal) > 0 {
|
||||
root := traversal[0].(TraverseRoot)
|
||||
traversal[0] = TraverseAttr{
|
||||
Name: root.Name,
|
||||
SrcRange: root.SrcRange,
|
||||
}
|
||||
}
|
||||
return traversal, diags
|
||||
}
|
||||
|
||||
// ExprAsKeyword attempts to interpret the given expression as a static keyword,
|
||||
// returning the keyword string if possible, and the empty string if not.
|
||||
//
|
||||
// A static keyword, for the sake of this function, is a single identifier.
|
||||
// For example, the following attribute has an expression that would produce
|
||||
// the keyword "foo":
|
||||
//
|
||||
// example = foo
|
||||
//
|
||||
// This function is a variant of AbsTraversalForExpr, which uses the same
|
||||
// interface on the given expression. This helper constrains the result
|
||||
// further by requiring only a single root identifier.
|
||||
//
|
||||
// This function is intended to be used with the following idiom, to recognize
|
||||
// situations where one of a fixed set of keywords is required and arbitrary
|
||||
// expressions are not allowed:
|
||||
//
|
||||
// switch hcl.ExprAsKeyword(expr) {
|
||||
// case "allow":
|
||||
// // (take suitable action for keyword "allow")
|
||||
// case "deny":
|
||||
// // (take suitable action for keyword "deny")
|
||||
// default:
|
||||
// diags = append(diags, &hcl.Diagnostic{
|
||||
// // ... "invalid keyword" diagnostic message ...
|
||||
// })
|
||||
// }
|
||||
//
|
||||
// The above approach will generate the same message for both the use of an
|
||||
// unrecognized keyword and for not using a keyword at all, which is usually
|
||||
// reasonable if the message specifies that the given value must be a keyword
|
||||
// from that fixed list.
|
||||
//
|
||||
// Note that in the native syntax the keywords "true", "false", and "null" are
|
||||
// recognized as literal values during parsing and so these reserved words
|
||||
// cannot not be accepted as keywords by this function.
|
||||
//
|
||||
// Since interpreting an expression as a keyword bypasses usual expression
|
||||
// evaluation, it should be used sparingly for situations where e.g. one of
|
||||
// a fixed set of keywords is used in a structural way in a special attribute
|
||||
// to affect the further processing of a block.
|
||||
func ExprAsKeyword(expr Expression) string {
|
||||
type asTraversal interface {
|
||||
AsTraversal() Traversal
|
||||
}
|
||||
|
||||
physExpr := UnwrapExpressionUntil(expr, func(expr Expression) bool {
|
||||
_, supported := expr.(asTraversal)
|
||||
return supported
|
||||
})
|
||||
|
||||
if asT, supported := physExpr.(asTraversal); supported {
|
||||
if traversal := asT.AsTraversal(); len(traversal) == 1 {
|
||||
return traversal.RootName()
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
|
@ -51,3 +51,28 @@ func ImpliedType(spec Spec) cty.Type {
|
|||
func SourceRange(body hcl.Body, spec Spec) hcl.Range {
|
||||
return sourceRange(body, nil, spec)
|
||||
}
|
||||
|
||||
// ChildBlockTypes returns a map of all of the child block types declared
|
||||
// by the given spec, with block type names as keys and the associated
|
||||
// nested body specs as values.
|
||||
func ChildBlockTypes(spec Spec) map[string]Spec {
|
||||
ret := map[string]Spec{}
|
||||
|
||||
// visitSameBodyChildren walks through the spec structure, calling
|
||||
// the given callback for each descendent spec encountered. We are
|
||||
// interested in the specs that reference attributes and blocks.
|
||||
var visit visitFunc
|
||||
visit = func(s Spec) {
|
||||
if bs, ok := s.(blockSpec); ok {
|
||||
for _, blockS := range bs.blockHeaderSchemata() {
|
||||
ret[blockS.Type] = bs.nestedSpec()
|
||||
}
|
||||
}
|
||||
|
||||
s.visitSameBodyChildren(visit)
|
||||
}
|
||||
|
||||
visit(spec)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
)
|
||||
|
||||
// A Spec is a description of how to decode a hcl.Body to a cty.Value.
|
||||
|
@ -52,6 +53,7 @@ type attrSpec interface {
|
|||
// blockSpec is implemented by specs that require blocks from the body.
|
||||
type blockSpec interface {
|
||||
blockHeaderSchemata() []hcl.BlockHeaderSchema
|
||||
nestedSpec() Spec
|
||||
}
|
||||
|
||||
// specNeedingVariables is implemented by specs that can use variables
|
||||
|
@ -298,6 +300,11 @@ func (s *BlockSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
|||
}
|
||||
}
|
||||
|
||||
// blockSpec implementation
|
||||
func (s *BlockSpec) nestedSpec() Spec {
|
||||
return s.Nested
|
||||
}
|
||||
|
||||
// specNeedingVariables implementation
|
||||
func (s *BlockSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
|
||||
var childBlock *hcl.Block
|
||||
|
@ -409,6 +416,11 @@ func (s *BlockListSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
|||
}
|
||||
}
|
||||
|
||||
// blockSpec implementation
|
||||
func (s *BlockListSpec) nestedSpec() Spec {
|
||||
return s.Nested
|
||||
}
|
||||
|
||||
// specNeedingVariables implementation
|
||||
func (s *BlockListSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
|
||||
var ret []hcl.Traversal
|
||||
|
@ -519,6 +531,11 @@ func (s *BlockSetSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
|||
}
|
||||
}
|
||||
|
||||
// blockSpec implementation
|
||||
func (s *BlockSetSpec) nestedSpec() Spec {
|
||||
return s.Nested
|
||||
}
|
||||
|
||||
// specNeedingVariables implementation
|
||||
func (s *BlockSetSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
|
||||
var ret []hcl.Traversal
|
||||
|
@ -631,6 +648,11 @@ func (s *BlockMapSpec) blockHeaderSchemata() []hcl.BlockHeaderSchema {
|
|||
}
|
||||
}
|
||||
|
||||
// blockSpec implementation
|
||||
func (s *BlockMapSpec) nestedSpec() Spec {
|
||||
return s.Nested
|
||||
}
|
||||
|
||||
// specNeedingVariables implementation
|
||||
func (s *BlockMapSpec) variablesNeeded(content *hcl.BodyContent) []hcl.Traversal {
|
||||
var ret []hcl.Traversal
|
||||
|
@ -857,3 +879,120 @@ func (s *DefaultSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockL
|
|||
// reasonable source range to return anyway.
|
||||
return s.Primary.sourceRange(content, blockLabels)
|
||||
}
|
||||
|
||||
// TransformExprSpec is a spec that wraps another and then evaluates a given
|
||||
// hcl.Expression on the result.
|
||||
//
|
||||
// The implied type of this spec is determined by evaluating the expression
|
||||
// with an unknown value of the nested spec's implied type, which may cause
|
||||
// the result to be imprecise. This spec should not be used in situations where
|
||||
// precise result type information is needed.
|
||||
type TransformExprSpec struct {
|
||||
Wrapped Spec
|
||||
Expr hcl.Expression
|
||||
TransformCtx *hcl.EvalContext
|
||||
VarName string
|
||||
}
|
||||
|
||||
func (s *TransformExprSpec) visitSameBodyChildren(cb visitFunc) {
|
||||
cb(s.Wrapped)
|
||||
}
|
||||
|
||||
func (s *TransformExprSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
wrappedVal, diags := s.Wrapped.decode(content, blockLabels, ctx)
|
||||
if diags.HasErrors() {
|
||||
// We won't try to run our function in this case, because it'll probably
|
||||
// generate confusing additional errors that will distract from the
|
||||
// root cause.
|
||||
return cty.UnknownVal(s.impliedType()), diags
|
||||
}
|
||||
|
||||
chiCtx := s.TransformCtx.NewChild()
|
||||
chiCtx.Variables = map[string]cty.Value{
|
||||
s.VarName: wrappedVal,
|
||||
}
|
||||
resultVal, resultDiags := s.Expr.Value(chiCtx)
|
||||
diags = append(diags, resultDiags...)
|
||||
return resultVal, diags
|
||||
}
|
||||
|
||||
func (s *TransformExprSpec) impliedType() cty.Type {
|
||||
wrappedTy := s.Wrapped.impliedType()
|
||||
chiCtx := s.TransformCtx.NewChild()
|
||||
chiCtx.Variables = map[string]cty.Value{
|
||||
s.VarName: cty.UnknownVal(wrappedTy),
|
||||
}
|
||||
resultVal, _ := s.Expr.Value(chiCtx)
|
||||
return resultVal.Type()
|
||||
}
|
||||
|
||||
func (s *TransformExprSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
// We'll just pass through our wrapped range here, even though that's
|
||||
// not super-accurate, because there's nothing better to return.
|
||||
return s.Wrapped.sourceRange(content, blockLabels)
|
||||
}
|
||||
|
||||
// TransformFuncSpec is a spec that wraps another and then evaluates a given
|
||||
// cty function with the result. The given function must expect exactly one
|
||||
// argument, where the result of the wrapped spec will be passed.
|
||||
//
|
||||
// The implied type of this spec is determined by type-checking the function
|
||||
// with an unknown value of the nested spec's implied type, which may cause
|
||||
// the result to be imprecise. This spec should not be used in situations where
|
||||
// precise result type information is needed.
|
||||
//
|
||||
// If the given function produces an error when run, this spec will produce
|
||||
// a non-user-actionable diagnostic message. It's the caller's responsibility
|
||||
// to ensure that the given function cannot fail for any non-error result
|
||||
// of the wrapped spec.
|
||||
type TransformFuncSpec struct {
|
||||
Wrapped Spec
|
||||
Func function.Function
|
||||
}
|
||||
|
||||
func (s *TransformFuncSpec) visitSameBodyChildren(cb visitFunc) {
|
||||
cb(s.Wrapped)
|
||||
}
|
||||
|
||||
func (s *TransformFuncSpec) decode(content *hcl.BodyContent, blockLabels []blockLabel, ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
wrappedVal, diags := s.Wrapped.decode(content, blockLabels, ctx)
|
||||
if diags.HasErrors() {
|
||||
// We won't try to run our function in this case, because it'll probably
|
||||
// generate confusing additional errors that will distract from the
|
||||
// root cause.
|
||||
return cty.UnknownVal(s.impliedType()), diags
|
||||
}
|
||||
|
||||
resultVal, err := s.Func.Call([]cty.Value{wrappedVal})
|
||||
if err != nil {
|
||||
// This is not a good example of a diagnostic because it is reporting
|
||||
// a programming error in the calling application, rather than something
|
||||
// an end-user could act on.
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Transform function failed",
|
||||
Detail: fmt.Sprintf("Decoder transform returned an error: %s", err),
|
||||
Subject: s.sourceRange(content, blockLabels).Ptr(),
|
||||
})
|
||||
return cty.UnknownVal(s.impliedType()), diags
|
||||
}
|
||||
|
||||
return resultVal, diags
|
||||
}
|
||||
|
||||
func (s *TransformFuncSpec) impliedType() cty.Type {
|
||||
wrappedTy := s.Wrapped.impliedType()
|
||||
resultTy, err := s.Func.ReturnType([]cty.Type{wrappedTy})
|
||||
if err != nil {
|
||||
// Should never happen with a correctly-configured spec
|
||||
return cty.DynamicPseudoType
|
||||
}
|
||||
|
||||
return resultTy
|
||||
}
|
||||
|
||||
func (s *TransformFuncSpec) sourceRange(content *hcl.BodyContent, blockLabels []blockLabel) hcl.Range {
|
||||
// We'll just pass through our wrapped range here, even though that's
|
||||
// not super-accurate, because there's nothing better to return.
|
||||
return s.Wrapped.sourceRange(content, blockLabels)
|
||||
}
|
||||
|
|
|
@ -3,13 +3,15 @@ package hcltest
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// MockBody returns a hcl.Body implementation that works in terms of a
|
||||
// caller-constructed hcl.BodyContent, thus avoiding the need to parse
|
||||
// a "real" zcl config file to use as input to a test.
|
||||
// a "real" HCL config file to use as input to a test.
|
||||
func MockBody(content *hcl.BodyContent) hcl.Body {
|
||||
return mockBody{content}
|
||||
}
|
||||
|
@ -149,6 +151,21 @@ func (e mockExprLiteral) StartRange() hcl.Range {
|
|||
return e.Range()
|
||||
}
|
||||
|
||||
// Implementation for hcl.ExprList
|
||||
func (e mockExprLiteral) ExprList() []hcl.Expression {
|
||||
v := e.V
|
||||
ty := v.Type()
|
||||
if v.IsKnown() && !v.IsNull() && (ty.IsListType() || ty.IsTupleType()) {
|
||||
ret := make([]hcl.Expression, 0, v.LengthInt())
|
||||
for it := v.ElementIterator(); it.Next(); {
|
||||
_, v := it.Element()
|
||||
ret = append(ret, MockExprLiteral(v))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MockExprVariable returns a hcl.Expression that evaluates to the value of
|
||||
// the variable with the given name.
|
||||
func MockExprVariable(name string) hcl.Expression {
|
||||
|
@ -197,6 +214,111 @@ func (e mockExprVariable) StartRange() hcl.Range {
|
|||
return e.Range()
|
||||
}
|
||||
|
||||
// Implementation for hcl.AbsTraversalForExpr and hcl.RelTraversalForExpr.
|
||||
func (e mockExprVariable) AsTraversal() hcl.Traversal {
|
||||
return hcl.Traversal{
|
||||
hcl.TraverseRoot{
|
||||
Name: string(e),
|
||||
SrcRange: e.Range(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// MockExprTraversal returns a hcl.Expression that evaluates the given
|
||||
// absolute traversal.
|
||||
func MockExprTraversal(traversal hcl.Traversal) hcl.Expression {
|
||||
return mockExprTraversal{
|
||||
Traversal: traversal,
|
||||
}
|
||||
}
|
||||
|
||||
// MockExprTraversalSrc is like MockExprTraversal except it takes a
|
||||
// traversal string as defined by the native syntax and parses it first.
|
||||
//
|
||||
// This method is primarily for testing with hard-coded traversal strings, so
|
||||
// it will panic if the given string is not syntactically correct.
|
||||
func MockExprTraversalSrc(src string) hcl.Expression {
|
||||
traversal, diags := hclsyntax.ParseTraversalAbs([]byte(src), "MockExprTraversal", hcl.Pos{})
|
||||
if diags.HasErrors() {
|
||||
panic("invalid traversal string")
|
||||
}
|
||||
return MockExprTraversal(traversal)
|
||||
}
|
||||
|
||||
type mockExprTraversal struct {
|
||||
Traversal hcl.Traversal
|
||||
}
|
||||
|
||||
func (e mockExprTraversal) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
return e.Traversal.TraverseAbs(ctx)
|
||||
}
|
||||
|
||||
func (e mockExprTraversal) Variables() []hcl.Traversal {
|
||||
return []hcl.Traversal{e.Traversal}
|
||||
}
|
||||
|
||||
func (e mockExprTraversal) Range() hcl.Range {
|
||||
return e.Traversal.SourceRange()
|
||||
}
|
||||
|
||||
func (e mockExprTraversal) StartRange() hcl.Range {
|
||||
return e.Range()
|
||||
}
|
||||
|
||||
// Implementation for hcl.AbsTraversalForExpr and hcl.RelTraversalForExpr.
|
||||
func (e mockExprTraversal) AsTraversal() hcl.Traversal {
|
||||
return e.Traversal
|
||||
}
|
||||
|
||||
func MockExprList(exprs []hcl.Expression) hcl.Expression {
|
||||
return mockExprList{
|
||||
Exprs: exprs,
|
||||
}
|
||||
}
|
||||
|
||||
type mockExprList struct {
|
||||
Exprs []hcl.Expression
|
||||
}
|
||||
|
||||
func (e mockExprList) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) {
|
||||
if len(e.Exprs) == 0 {
|
||||
return cty.ListValEmpty(cty.DynamicPseudoType), nil
|
||||
}
|
||||
vals := make([]cty.Value, 0, len(e.Exprs))
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
for _, expr := range e.Exprs {
|
||||
val, valDiags := expr.Value(ctx)
|
||||
diags = append(diags, valDiags...)
|
||||
vals = append(vals, val)
|
||||
}
|
||||
|
||||
return cty.ListVal(vals), diags
|
||||
}
|
||||
|
||||
func (e mockExprList) Variables() []hcl.Traversal {
|
||||
var traversals []hcl.Traversal
|
||||
for _, expr := range e.Exprs {
|
||||
traversals = append(traversals, expr.Variables()...)
|
||||
}
|
||||
return traversals
|
||||
}
|
||||
|
||||
func (e mockExprList) Range() hcl.Range {
|
||||
return hcl.Range{
|
||||
Filename: "MockExprList",
|
||||
}
|
||||
}
|
||||
|
||||
func (e mockExprList) StartRange() hcl.Range {
|
||||
return e.Range()
|
||||
}
|
||||
|
||||
// Implementation for hcl.ExprList
|
||||
func (e mockExprList) ExprList() []hcl.Expression {
|
||||
return e.Exprs
|
||||
}
|
||||
|
||||
// MockAttrs constructs and returns a hcl.Attributes map with attributes
|
||||
// derived from the given expression map.
|
||||
//
|
||||
|
|
|
@ -82,7 +82,7 @@ func (n *Body) AppendUnstructuredTokens(seq *TokenSeq) {
|
|||
// given name, or returns nil if there is currently no matching attribute.
|
||||
//
|
||||
// A valid AST has only one definition of each attribute, but that constraint
|
||||
// is not enforced in the zclwrite AST, so a tree that has been mutated by
|
||||
// is not enforced in the hclwrite AST, so a tree that has been mutated by
|
||||
// other calls may contain additional matching attributes that cannot be seen
|
||||
// by this method.
|
||||
func (n *Body) FindAttribute(name string) *Attribute {
|
||||
|
|
|
@ -49,7 +49,7 @@ func formatIndent(lines []formatLine) {
|
|||
|
||||
// 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 zcl uses.
|
||||
// which should be more than enough for reasonable HCL uses.
|
||||
indents := make([]int, 0, 10)
|
||||
|
||||
for i := range lines {
|
||||
|
|
|
@ -3,18 +3,18 @@ package hclwrite
|
|||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
"github.com/hashicorp/hcl2/hcl"
|
||||
"github.com/hashicorp/hcl2/hcl/hclsyntax"
|
||||
)
|
||||
|
||||
// Our "parser" here is actually not doing any parsing of its own. Instead,
|
||||
// it leans on the native parser in zclsyntax, and then uses the source ranges
|
||||
// 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 zclsyntax is considered to be the "main case",
|
||||
// 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
|
||||
|
@ -30,7 +30,7 @@ func parse(src []byte, filename string, start hcl.Pos) (*File, hcl.Diagnostics)
|
|||
return nil, diags
|
||||
}
|
||||
|
||||
// To do our work here, we use the "native" tokens (those from zclsyntax)
|
||||
// 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
|
||||
|
@ -333,7 +333,7 @@ func parseExpression(nativeExpr hclsyntax.Expression, from inputTokens) *Express
|
|||
}
|
||||
}
|
||||
|
||||
// writerTokens takes a sequence of tokens as produced by the main zclsyntax
|
||||
// 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.
|
||||
//
|
||||
|
@ -389,7 +389,7 @@ func writerTokens(nativeTokens hclsyntax.Tokens) Tokens {
|
|||
// true then it will make a best effort that may produce strange results at
|
||||
// the boundaries.
|
||||
//
|
||||
// Native zclsyntax tokens are used here, because they contain the necessary
|
||||
// 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
|
||||
|
@ -488,7 +488,7 @@ func partitionLineEndTokens(toks hclsyntax.Tokens) (afterComment, afterNewline i
|
|||
return len(toks), len(toks)
|
||||
}
|
||||
|
||||
// lexConfig uses the zclsyntax scanner to get a token stream and then
|
||||
// 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
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/hashicorp/hcl2/hcl"
|
||||
)
|
||||
|
||||
// ParseConfig interprets the given source bytes into a *zclwrite.File. The
|
||||
// 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) {
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
// TokenGen is an abstract type that can append tokens to a list. It is the
|
||||
// low-level foundation underlying the zclwrite AST; the AST provides a
|
||||
// low-level foundation underlying the hclwrite AST; the AST provides a
|
||||
// convenient abstraction over raw token sequences to facilitate common tasks,
|
||||
// but it's also possible to directly manipulate the tree of token generators
|
||||
// to make changes that the AST API doesn't directly allow.
|
||||
|
@ -22,7 +22,7 @@ type TokenGen interface {
|
|||
type TokenCallback func(*Token)
|
||||
|
||||
// Token is a single sequence of bytes annotated with a type. It is similar
|
||||
// in purpose to zclsyntax.Token, but discards the source position information
|
||||
// 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
|
||||
|
|
|
@ -1697,56 +1697,56 @@
|
|||
{
|
||||
"checksumSHA1": "6kxMiZSmgazD/CZgmnEeEMJSAOM=",
|
||||
"path": "github.com/hashicorp/hcl2/gohcl",
|
||||
"revision": "44bad6dbf5490f5da17ec991e664df3d017b706f",
|
||||
"revisionTime": "2017-10-03T23:27:34Z"
|
||||
"revision": "5ca9713bf06addcefc0a4e16f779e43a2c88570c",
|
||||
"revisionTime": "2018-02-05T02:55:09Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "TsNlThzf92FMwcnM4Fc0mArHroU=",
|
||||
"checksumSHA1": "l2zkxDVi2EUwFdvsVcIfyuOr4zo=",
|
||||
"path": "github.com/hashicorp/hcl2/hcl",
|
||||
"revision": "44bad6dbf5490f5da17ec991e664df3d017b706f",
|
||||
"revisionTime": "2017-10-03T23:27:34Z"
|
||||
"revision": "5ca9713bf06addcefc0a4e16f779e43a2c88570c",
|
||||
"revisionTime": "2018-02-05T02:55:09Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "+Dv8V2cfl7Vy6rUklhXj5Cli8aU=",
|
||||
"checksumSHA1": "iLOUzHOej23ORpmbXAndg5Ft5H0=",
|
||||
"path": "github.com/hashicorp/hcl2/hcl/hclsyntax",
|
||||
"revision": "44bad6dbf5490f5da17ec991e664df3d017b706f",
|
||||
"revisionTime": "2017-10-03T23:27:34Z"
|
||||
"revision": "5ca9713bf06addcefc0a4e16f779e43a2c88570c",
|
||||
"revisionTime": "2018-02-05T02:55:09Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "GAArMzjaoFNPa7HFnhjZmaeBZII=",
|
||||
"checksumSHA1": "O8jJfHiwuQFmAo0ivcBhni4pWyg=",
|
||||
"path": "github.com/hashicorp/hcl2/hcl/json",
|
||||
"revision": "44bad6dbf5490f5da17ec991e664df3d017b706f",
|
||||
"revisionTime": "2017-10-03T23:27:34Z"
|
||||
"revision": "5ca9713bf06addcefc0a4e16f779e43a2c88570c",
|
||||
"revisionTime": "2018-02-05T02:55:09Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "u6YPoPz3GflgHb1dN1YN8nCWAXY=",
|
||||
"checksumSHA1": "672O/GQ9z+OFsG3eHLKq1yg3ZGM=",
|
||||
"path": "github.com/hashicorp/hcl2/hcldec",
|
||||
"revision": "44bad6dbf5490f5da17ec991e664df3d017b706f",
|
||||
"revisionTime": "2017-10-03T23:27:34Z"
|
||||
"revision": "5ca9713bf06addcefc0a4e16f779e43a2c88570c",
|
||||
"revisionTime": "2018-02-05T02:55:09Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "sySYF9Ew71VS/LfrG+s/0jK+1VQ=",
|
||||
"path": "github.com/hashicorp/hcl2/hcled",
|
||||
"revision": "44bad6dbf5490f5da17ec991e664df3d017b706f",
|
||||
"revisionTime": "2017-10-03T23:27:34Z"
|
||||
"revision": "5ca9713bf06addcefc0a4e16f779e43a2c88570c",
|
||||
"revisionTime": "2018-02-05T02:55:09Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "IzmftuG99BqNhbFGhxZaGwtiMtM=",
|
||||
"path": "github.com/hashicorp/hcl2/hclparse",
|
||||
"revision": "44bad6dbf5490f5da17ec991e664df3d017b706f",
|
||||
"revisionTime": "2017-10-03T23:27:34Z"
|
||||
"revision": "5ca9713bf06addcefc0a4e16f779e43a2c88570c",
|
||||
"revisionTime": "2018-02-05T02:55:09Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "4supppf3CMdAEUEDrXP8niXvAR0=",
|
||||
"checksumSHA1": "v5qx2XghQ+EtvFLa4a0Efjiwt9I=",
|
||||
"path": "github.com/hashicorp/hcl2/hcltest",
|
||||
"revision": "44bad6dbf5490f5da17ec991e664df3d017b706f",
|
||||
"revisionTime": "2017-10-03T23:27:34Z"
|
||||
"revision": "5ca9713bf06addcefc0a4e16f779e43a2c88570c",
|
||||
"revisionTime": "2018-02-05T02:55:09Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "p+dun/Fx4beswXTtoEjVnwDJE+Y=",
|
||||
"checksumSHA1": "9UCSLRG+TEAsNKOZJUaJj/7d6r8=",
|
||||
"path": "github.com/hashicorp/hcl2/hclwrite",
|
||||
"revision": "44bad6dbf5490f5da17ec991e664df3d017b706f",
|
||||
"revisionTime": "2017-10-03T23:27:34Z"
|
||||
"revision": "5ca9713bf06addcefc0a4e16f779e43a2c88570c",
|
||||
"revisionTime": "2018-02-05T02:55:09Z"
|
||||
},
|
||||
{
|
||||
"checksumSHA1": "M09yxoBoCEtG7EcHR8aEWLzMMJc=",
|
||||
|
|
Loading…
Reference in New Issue