-
Notifications
You must be signed in to change notification settings - Fork 601
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
hclsyntax: Introduce token-based parse methods #383
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,8 @@ | ||
package hclsyntax | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/hashicorp/hcl/v2" | ||
) | ||
|
||
|
@@ -36,6 +38,98 @@ func ParseConfig(src []byte, filename string, start hcl.Pos) (*hcl.File, hcl.Dia | |
}, diags | ||
} | ||
|
||
// ParseBodyFromTokens parses given tokens as a body of a whole HCL config file, | ||
// returning a *Body representing its contents. | ||
func ParseBodyFromTokens(tokens Tokens, end TokenType) (*Body, hcl.Diagnostics) { | ||
peeker := newPeeker(tokens, false) | ||
parser := &parser{peeker: peeker} | ||
return parser.ParseBody(end) | ||
} | ||
|
||
// ParseBodyItemFromTokens parses given tokens as a body item | ||
// such as an attribute or a block, returning such item as Node | ||
func ParseBodyItemFromTokens(tokens Tokens) (Node, hcl.Diagnostics) { | ||
if len(tokens) == 0 { | ||
return nil, nil | ||
} | ||
|
||
peeker := newPeeker(tokens, false) | ||
|
||
// Sanity checks to avoid surprises | ||
firstToken := peeker.Peek() | ||
if firstToken.Type != TokenIdent { | ||
return nil, hcl.Diagnostics{ | ||
&hcl.Diagnostic{ | ||
Severity: hcl.DiagError, | ||
Summary: "Identifier not found", | ||
Detail: fmt.Sprintf("Expected definition to start with an identifier, %s found", | ||
firstToken.Type), | ||
Subject: &firstToken.Range, | ||
}, | ||
} | ||
} | ||
lastToken := peeker.lastToken() | ||
if lastToken.Type != TokenEOF && | ||
lastToken.Type != TokenNewline { | ||
return nil, hcl.Diagnostics{ | ||
&hcl.Diagnostic{ | ||
Severity: hcl.DiagError, | ||
Summary: "Unterminated definition", | ||
Detail: fmt.Sprintf("Expected definition terminated either by a newline or EOF, %s found", | ||
lastToken.Type), | ||
Subject: &lastToken.Range, | ||
}, | ||
} | ||
} | ||
|
||
parser := &parser{peeker: peeker} | ||
return parser.ParseBodyItem() | ||
} | ||
|
||
// ParseBlockFromTokens parses given tokens as a block, returning | ||
// diagnostic error in case the body item isn't a block | ||
func ParseBlockFromTokens(tokens Tokens) (*Block, hcl.Diagnostics) { | ||
bi, diags := ParseBodyItemFromTokens(tokens) | ||
if bi == nil { | ||
return nil, diags | ||
} | ||
|
||
block, ok := bi.(*Block) | ||
if !ok { | ||
rng := bi.Range() | ||
diags = append(diags, &hcl.Diagnostic{ | ||
Severity: hcl.DiagError, | ||
Summary: fmt.Sprintf("Unexpected definition (%T)", bi), | ||
Detail: fmt.Sprintf("Expected a block definition, but found %T instead", bi), | ||
Subject: &rng, | ||
}) | ||
} | ||
|
||
return block, diags | ||
} | ||
|
||
// ParseAttributeFromTokens parses given tokens as an attribute | ||
// diagnostic error in case the body item isn't an attribute | ||
func ParseAttributeFromTokens(tokens Tokens) (*Attribute, hcl.Diagnostics) { | ||
bi, diags := ParseBodyItemFromTokens(tokens) | ||
if bi == nil { | ||
return nil, diags | ||
} | ||
|
||
block, ok := bi.(*Attribute) | ||
if !ok { | ||
rng := bi.Range() | ||
diags = append(diags, &hcl.Diagnostic{ | ||
Severity: hcl.DiagError, | ||
Summary: fmt.Sprintf("Unexpected definition (%T)", bi), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Usually we've written errors like this with an end-user target audience in mind, so that a caller can use a call to this function to represent the assertion "the user should have written an attribute" and automatically get a good error message if the user didn't provide one. With that said, I'm not totally sure about that framing for these new functions. It could well be that we consider it a programming error on the part of the caller to pass in tokens representing a block here, in which case I suppose this could be okay although in cases like that HCL has typically used As a compromise, what do you think about taking the text of the message HCL would normally return if the schema calls for attribute syntax but the user wrote a block, and reducing it to fit what we can determine here without a schema? For example:
(To do this would, I realize, require type-asserting the (I have similar feedback for the opposite case of |
||
Detail: fmt.Sprintf("Expected an attribute, but found %T instead", bi), | ||
Subject: &rng, | ||
}) | ||
} | ||
|
||
return block, 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) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's say "Initial checks" instead here, for inclusiveness. ❤️