6c4344623b
The main HCL package is more visible this way, and so it's easier than having to pick it out from dozens of other package directories.
172 lines
6.6 KiB
Go
172 lines
6.6 KiB
Go
package hclsyntax
|
|
|
|
import (
|
|
"github.com/hashicorp/hcl/v2"
|
|
)
|
|
|
|
// ParseConfig parses the given buffer as a whole HCL config file, returning
|
|
// a *hcl.File representing its contents. If HasErrors called on the returned
|
|
// diagnostics returns true, the returned body is likely to be incomplete
|
|
// and should therefore be used with care.
|
|
//
|
|
// The body in the returned file has dynamic type *hclsyntax.Body, so callers
|
|
// may freely type-assert this to get access to the full hclsyntax API in
|
|
// situations where detailed access is required. However, most common use-cases
|
|
// should be served using the hcl.Body interface to ensure compatibility with
|
|
// other configurationg syntaxes, such as JSON.
|
|
func ParseConfig(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Diagnostics) {
|
|
tokens, diags := LexConfig(src, filename, start)
|
|
peeker := newPeeker(tokens, false)
|
|
parser := &parser{peeker: peeker}
|
|
body, parseDiags := parser.ParseBody(TokenEOF)
|
|
diags = append(diags, parseDiags...)
|
|
|
|
// Panic if the parser uses incorrect stack discipline with the peeker's
|
|
// newlines stack, since otherwise it will produce confusing downstream
|
|
// errors.
|
|
peeker.AssertEmptyIncludeNewlinesStack()
|
|
|
|
return &hcl.File{
|
|
Body: body,
|
|
Bytes: src,
|
|
|
|
Nav: navigation{
|
|
root: body,
|
|
},
|
|
}, diags
|
|
}
|
|
|
|
// ParseExpression parses the given buffer as a standalone HCL expression,
|
|
// returning it as an instance of Expression.
|
|
func ParseExpression(src []byte, filename string, start hcl.Pos) (Expression, hcl.Diagnostics) {
|
|
tokens, diags := LexExpression(src, filename, start)
|
|
peeker := newPeeker(tokens, false)
|
|
parser := &parser{peeker: peeker}
|
|
|
|
// Bare expressions are always parsed in "ignore newlines" mode, as if
|
|
// they were wrapped in parentheses.
|
|
parser.PushIncludeNewlines(false)
|
|
|
|
expr, parseDiags := parser.ParseExpression()
|
|
diags = append(diags, parseDiags...)
|
|
|
|
next := parser.Peek()
|
|
if next.Type != TokenEOF && !parser.recovery {
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Extra characters after expression",
|
|
Detail: "An expression was successfully parsed, but extra characters were found after it.",
|
|
Subject: &next.Range,
|
|
})
|
|
}
|
|
|
|
parser.PopIncludeNewlines()
|
|
|
|
// Panic if the parser uses incorrect stack discipline with the peeker's
|
|
// newlines stack, since otherwise it will produce confusing downstream
|
|
// errors.
|
|
peeker.AssertEmptyIncludeNewlinesStack()
|
|
|
|
return expr, diags
|
|
}
|
|
|
|
// ParseTemplate parses the given buffer as a standalone HCL template,
|
|
// returning it as an instance of Expression.
|
|
func ParseTemplate(src []byte, filename string, start hcl.Pos) (Expression, hcl.Diagnostics) {
|
|
tokens, diags := LexTemplate(src, filename, start)
|
|
peeker := newPeeker(tokens, false)
|
|
parser := &parser{peeker: peeker}
|
|
expr, parseDiags := parser.ParseTemplate()
|
|
diags = append(diags, parseDiags...)
|
|
|
|
// Panic if the parser uses incorrect stack discipline with the peeker's
|
|
// newlines stack, since otherwise it will produce confusing downstream
|
|
// errors.
|
|
peeker.AssertEmptyIncludeNewlinesStack()
|
|
|
|
return expr, diags
|
|
}
|
|
|
|
// ParseTraversalAbs parses the given buffer as a standalone absolute traversal.
|
|
//
|
|
// Parsing as a traversal is more limited than parsing as an expession since
|
|
// it allows only attribute and indexing operations on variables. Traverals
|
|
// are useful as a syntax for referring to objects without necessarily
|
|
// evaluating them.
|
|
func ParseTraversalAbs(src []byte, filename string, start hcl.Pos) (hcl.Traversal, hcl.Diagnostics) {
|
|
tokens, diags := LexExpression(src, filename, start)
|
|
peeker := newPeeker(tokens, false)
|
|
parser := &parser{peeker: peeker}
|
|
|
|
// Bare traverals are always parsed in "ignore newlines" mode, as if
|
|
// they were wrapped in parentheses.
|
|
parser.PushIncludeNewlines(false)
|
|
|
|
expr, parseDiags := parser.ParseTraversalAbs()
|
|
diags = append(diags, parseDiags...)
|
|
|
|
parser.PopIncludeNewlines()
|
|
|
|
// Panic if the parser uses incorrect stack discipline with the peeker's
|
|
// newlines stack, since otherwise it will produce confusing downstream
|
|
// errors.
|
|
peeker.AssertEmptyIncludeNewlinesStack()
|
|
|
|
return expr, diags
|
|
}
|
|
|
|
// LexConfig performs lexical analysis on the given buffer, treating it as a
|
|
// whole HCL config file, and returns the resulting tokens.
|
|
//
|
|
// Only minimal validation is done during lexical analysis, so the returned
|
|
// diagnostics may include errors about lexical issues such as bad character
|
|
// encodings or unrecognized characters, but full parsing is required to
|
|
// detect _all_ syntax errors.
|
|
func LexConfig(src []byte, filename string, start hcl.Pos) (Tokens, hcl.Diagnostics) {
|
|
tokens := scanTokens(src, filename, start, scanNormal)
|
|
diags := checkInvalidTokens(tokens)
|
|
return tokens, diags
|
|
}
|
|
|
|
// LexExpression performs lexical analysis on the given buffer, treating it as
|
|
// a standalone HCL expression, and returns the resulting tokens.
|
|
//
|
|
// Only minimal validation is done during lexical analysis, so the returned
|
|
// diagnostics may include errors about lexical issues such as bad character
|
|
// encodings or unrecognized characters, but full parsing is required to
|
|
// detect _all_ syntax errors.
|
|
func LexExpression(src []byte, filename string, start hcl.Pos) (Tokens, hcl.Diagnostics) {
|
|
// This is actually just the same thing as LexConfig, since configs
|
|
// and expressions lex in the same way.
|
|
tokens := scanTokens(src, filename, start, scanNormal)
|
|
diags := checkInvalidTokens(tokens)
|
|
return tokens, diags
|
|
}
|
|
|
|
// LexTemplate performs lexical analysis on the given buffer, treating it as a
|
|
// standalone HCL template, and returns the resulting tokens.
|
|
//
|
|
// Only minimal validation is done during lexical analysis, so the returned
|
|
// diagnostics may include errors about lexical issues such as bad character
|
|
// encodings or unrecognized characters, but full parsing is required to
|
|
// detect _all_ syntax errors.
|
|
func LexTemplate(src []byte, filename string, start hcl.Pos) (Tokens, hcl.Diagnostics) {
|
|
tokens := scanTokens(src, filename, start, scanTemplate)
|
|
diags := checkInvalidTokens(tokens)
|
|
return tokens, diags
|
|
}
|
|
|
|
// ValidIdentifier tests if the given string could be a valid identifier in
|
|
// a native syntax expression.
|
|
//
|
|
// This is useful when accepting names from the user that will be used as
|
|
// variable or attribute names in the scope, to ensure that any name chosen
|
|
// will be traversable using the variable or attribute traversal syntax.
|
|
func ValidIdentifier(s string) bool {
|
|
// This is a kinda-expensive way to do something pretty simple, but it
|
|
// is easiest to do with our existing scanner-related infrastructure here
|
|
// and nobody should be validating identifiers in a tight loop.
|
|
tokens := scanTokens([]byte(s), "", hcl.Pos{}, scanIdentOnly)
|
|
return len(tokens) == 2 && tokens[0].Type == TokenIdent && tokens[1].Type == TokenEOF
|
|
}
|