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 }