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:
Martin Atkins 2018-02-02 11:15:48 -08:00
parent 13fa73c63e
commit b865d62bb8
37 changed files with 3532 additions and 679 deletions

View File

@ -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'})

37
vendor/github.com/hashicorp/hcl2/hcl/expr_list.go generated vendored Normal file
View File

@ -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(),
},
}
}

68
vendor/github.com/hashicorp/hcl2/hcl/expr_unwrap.go generated vendored Normal file
View File

@ -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
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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(),
})

View File

@ -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
}

View 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
}

View 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

View File

@ -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))
}

View File

@ -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

View File

@ -110,6 +110,7 @@ type scanMode int
const (
scanNormal scanMode = iota
scanTemplate
scanIdentOnly
)
type tokenAccum struct {

View File

@ -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) + ")"
}

View File

@ -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 {

View File

@ -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",

View File

@ -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
}

View File

@ -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.

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}

148
vendor/github.com/hashicorp/hcl2/hcl/pos_scanner.go generated vendored Normal file
View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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 ""
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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.
//

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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) {

View File

@ -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

48
vendor/vendor.json vendored
View File

@ -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=",