2017-09-11 23:40:37 +00:00
|
|
|
package hclsyntax
|
2017-05-29 23:17:07 +00:00
|
|
|
|
|
|
|
import (
|
2019-09-09 23:08:19 +00:00
|
|
|
"github.com/hashicorp/hcl/v2"
|
2017-05-29 23:17:07 +00:00
|
|
|
)
|
|
|
|
|
2018-01-27 18:42:01 +00:00
|
|
|
// ParseConfig parses the given buffer as a whole HCL config file, returning
|
2017-09-11 23:40:37 +00:00
|
|
|
// a *hcl.File representing its contents. If HasErrors called on the returned
|
2017-05-30 00:19:23 +00:00
|
|
|
// diagnostics returns true, the returned body is likely to be incomplete
|
|
|
|
// and should therefore be used with care.
|
2017-05-30 15:03:38 +00:00
|
|
|
//
|
2018-01-24 05:54:38 +00:00
|
|
|
// 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
|
2017-05-30 15:03:38 +00:00
|
|
|
// situations where detailed access is required. However, most common use-cases
|
2017-09-11 23:40:37 +00:00
|
|
|
// should be served using the hcl.Body interface to ensure compatibility with
|
2017-05-30 15:03:38 +00:00
|
|
|
// other configurationg syntaxes, such as JSON.
|
2017-09-11 23:40:37 +00:00
|
|
|
func ParseConfig(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Diagnostics) {
|
2017-06-04 14:34:26 +00:00
|
|
|
tokens, diags := LexConfig(src, filename, start)
|
2017-05-30 02:28:10 +00:00
|
|
|
peeker := newPeeker(tokens, false)
|
2017-05-30 14:51:55 +00:00
|
|
|
parser := &parser{peeker: peeker}
|
2017-06-04 14:34:26 +00:00
|
|
|
body, parseDiags := parser.ParseBody(TokenEOF)
|
|
|
|
diags = append(diags, parseDiags...)
|
2018-02-17 01:37:22 +00:00
|
|
|
|
|
|
|
// Panic if the parser uses incorrect stack discipline with the peeker's
|
|
|
|
// newlines stack, since otherwise it will produce confusing downstream
|
|
|
|
// errors.
|
|
|
|
peeker.AssertEmptyIncludeNewlinesStack()
|
|
|
|
|
2017-09-11 23:40:37 +00:00
|
|
|
return &hcl.File{
|
2017-05-30 15:03:38 +00:00
|
|
|
Body: body,
|
|
|
|
Bytes: src,
|
2017-06-04 16:52:16 +00:00
|
|
|
|
|
|
|
Nav: navigation{
|
|
|
|
root: body,
|
|
|
|
},
|
2017-05-30 15:03:38 +00:00
|
|
|
}, diags
|
2017-05-30 00:19:23 +00:00
|
|
|
}
|
|
|
|
|
2018-01-27 18:42:01 +00:00
|
|
|
// ParseExpression parses the given buffer as a standalone HCL expression,
|
2017-05-30 00:19:23 +00:00
|
|
|
// returning it as an instance of Expression.
|
2017-09-11 23:40:37 +00:00
|
|
|
func ParseExpression(src []byte, filename string, start hcl.Pos) (Expression, hcl.Diagnostics) {
|
2017-06-04 14:34:26 +00:00
|
|
|
tokens, diags := LexExpression(src, filename, start)
|
2017-06-01 02:27:16 +00:00
|
|
|
peeker := newPeeker(tokens, false)
|
|
|
|
parser := &parser{peeker: peeker}
|
2017-06-02 14:26:45 +00:00
|
|
|
|
|
|
|
// Bare expressions are always parsed in "ignore newlines" mode, as if
|
|
|
|
// they were wrapped in parentheses.
|
|
|
|
parser.PushIncludeNewlines(false)
|
|
|
|
|
2017-06-04 14:34:26 +00:00
|
|
|
expr, parseDiags := parser.ParseExpression()
|
|
|
|
diags = append(diags, parseDiags...)
|
2017-06-01 02:27:16 +00:00
|
|
|
|
|
|
|
next := parser.Peek()
|
2017-06-02 14:26:45 +00:00
|
|
|
if next.Type != TokenEOF && !parser.recovery {
|
2017-09-11 23:40:37 +00:00
|
|
|
diags = append(diags, &hcl.Diagnostic{
|
|
|
|
Severity: hcl.DiagError,
|
2017-06-01 02:27:16 +00:00
|
|
|
Summary: "Extra characters after expression",
|
|
|
|
Detail: "An expression was successfully parsed, but extra characters were found after it.",
|
|
|
|
Subject: &next.Range,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-02-17 01:37:22 +00:00
|
|
|
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()
|
|
|
|
|
2017-06-01 02:27:16 +00:00
|
|
|
return expr, diags
|
2017-05-30 00:19:23 +00:00
|
|
|
}
|
|
|
|
|
2018-01-27 18:42:01 +00:00
|
|
|
// ParseTemplate parses the given buffer as a standalone HCL template,
|
2017-05-30 00:19:23 +00:00
|
|
|
// returning it as an instance of Expression.
|
2017-09-11 23:40:37 +00:00
|
|
|
func ParseTemplate(src []byte, filename string, start hcl.Pos) (Expression, hcl.Diagnostics) {
|
2017-06-04 14:34:26 +00:00
|
|
|
tokens, diags := LexTemplate(src, filename, start)
|
2017-06-01 15:10:04 +00:00
|
|
|
peeker := newPeeker(tokens, false)
|
|
|
|
parser := &parser{peeker: peeker}
|
2017-06-11 15:39:40 +00:00
|
|
|
expr, parseDiags := parser.ParseTemplate()
|
2017-06-04 14:34:26 +00:00
|
|
|
diags = append(diags, parseDiags...)
|
2018-02-17 01:37:22 +00:00
|
|
|
|
|
|
|
// Panic if the parser uses incorrect stack discipline with the peeker's
|
|
|
|
// newlines stack, since otherwise it will produce confusing downstream
|
|
|
|
// errors.
|
|
|
|
peeker.AssertEmptyIncludeNewlinesStack()
|
|
|
|
|
2017-06-04 14:34:26 +00:00
|
|
|
return expr, diags
|
2017-05-30 00:19:23 +00:00
|
|
|
}
|
|
|
|
|
2017-06-22 15:00:24 +00:00
|
|
|
// 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.
|
2017-09-11 23:40:37 +00:00
|
|
|
func ParseTraversalAbs(src []byte, filename string, start hcl.Pos) (hcl.Traversal, hcl.Diagnostics) {
|
2017-06-22 15:00:24 +00:00
|
|
|
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...)
|
2018-02-17 01:37:22 +00:00
|
|
|
|
|
|
|
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()
|
|
|
|
|
2017-06-22 15:00:24 +00:00
|
|
|
return expr, diags
|
|
|
|
}
|
|
|
|
|
2017-05-29 23:17:07 +00:00
|
|
|
// LexConfig performs lexical analysis on the given buffer, treating it as a
|
2018-01-27 18:42:01 +00:00
|
|
|
// whole HCL config file, and returns the resulting tokens.
|
2017-06-04 14:34:26 +00:00
|
|
|
//
|
|
|
|
// 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.
|
2017-09-11 23:40:37 +00:00
|
|
|
func LexConfig(src []byte, filename string, start hcl.Pos) (Tokens, hcl.Diagnostics) {
|
2017-06-04 14:34:26 +00:00
|
|
|
tokens := scanTokens(src, filename, start, scanNormal)
|
|
|
|
diags := checkInvalidTokens(tokens)
|
|
|
|
return tokens, diags
|
2017-05-29 23:17:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LexExpression performs lexical analysis on the given buffer, treating it as
|
2018-01-27 18:42:01 +00:00
|
|
|
// a standalone HCL expression, and returns the resulting tokens.
|
2017-06-04 14:34:26 +00:00
|
|
|
//
|
|
|
|
// 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.
|
2017-09-11 23:40:37 +00:00
|
|
|
func LexExpression(src []byte, filename string, start hcl.Pos) (Tokens, hcl.Diagnostics) {
|
2017-05-29 23:17:07 +00:00
|
|
|
// This is actually just the same thing as LexConfig, since configs
|
|
|
|
// and expressions lex in the same way.
|
2017-06-04 14:34:26 +00:00
|
|
|
tokens := scanTokens(src, filename, start, scanNormal)
|
|
|
|
diags := checkInvalidTokens(tokens)
|
|
|
|
return tokens, diags
|
2017-05-29 23:17:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// LexTemplate performs lexical analysis on the given buffer, treating it as a
|
2018-01-27 18:42:01 +00:00
|
|
|
// standalone HCL template, and returns the resulting tokens.
|
2017-06-04 14:34:26 +00:00
|
|
|
//
|
|
|
|
// 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.
|
2017-09-11 23:40:37 +00:00
|
|
|
func LexTemplate(src []byte, filename string, start hcl.Pos) (Tokens, hcl.Diagnostics) {
|
2017-06-04 14:34:26 +00:00
|
|
|
tokens := scanTokens(src, filename, start, scanTemplate)
|
|
|
|
diags := checkInvalidTokens(tokens)
|
|
|
|
return tokens, diags
|
2017-05-29 23:17:07 +00:00
|
|
|
}
|
2018-02-02 16:09:40 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|