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