mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-21 18:10:30 -04:00
96 lines
3.4 KiB
Go
96 lines
3.4 KiB
Go
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package repl
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
)
|
|
|
|
// ExpressionEntryCouldContinue is a helper for terraform console's interactive
|
|
// mode which serves as a heuristic for whether it seems like the author might
|
|
// be trying to split an expression over multiple lines of input.
|
|
//
|
|
// The current heuristic is whether there's at least one bracketing delimiter
|
|
// that isn't closed, but only if any closing brackets already present are
|
|
// properly balanced.
|
|
//
|
|
// This function also always returns false if the last line entered is empty,
|
|
// because that seems likely to represent a user trying to force Terraform to
|
|
// accept something that didn't pass the heuristic for some reason, at which
|
|
// point Terraform can try to evaluate the expression and return an error if
|
|
// it's invalid syntax.
|
|
func ExpressionEntryCouldContinue(linesSoFar []string) bool {
|
|
if len(linesSoFar) == 0 || strings.TrimSpace(linesSoFar[len(linesSoFar)-1]) == "" {
|
|
// If there's no input at all or if the last line is empty other than
|
|
// spaces, we assume the user is trying to force Terraform to evaluate
|
|
// what they entered so far without any further continuation.
|
|
return false
|
|
}
|
|
|
|
// We use capacity 8 here as a compromise assuming that most reasonable
|
|
// input entered at the console prompt will not use more than eight
|
|
// levels of nesting, but even if it does then we'll just reallocate the
|
|
// slice and so it's not a big deal.
|
|
delimStack := make([]hclsyntax.TokenType, 0, 8)
|
|
push := func(typ hclsyntax.TokenType) {
|
|
delimStack = append(delimStack, typ)
|
|
}
|
|
pop := func() hclsyntax.TokenType {
|
|
if len(delimStack) == 0 {
|
|
return hclsyntax.TokenInvalid
|
|
}
|
|
ret := delimStack[len(delimStack)-1]
|
|
delimStack = delimStack[:len(delimStack)-1]
|
|
return ret
|
|
}
|
|
// We need to scan this all as one string because the HCL lexer has a few
|
|
// special cases where it tracks open/close state itself, such as in heredocs.
|
|
all := strings.Join(linesSoFar, "\n") + "\n"
|
|
toks, diags := hclsyntax.LexExpression([]byte(all), "", hcl.InitialPos)
|
|
if diags.HasErrors() {
|
|
return false // bail early if the input is already invalid
|
|
}
|
|
for _, tok := range toks {
|
|
switch tok.Type {
|
|
case hclsyntax.TokenOBrace, hclsyntax.TokenOBrack, hclsyntax.TokenOParen, hclsyntax.TokenOHeredoc, hclsyntax.TokenTemplateInterp, hclsyntax.TokenTemplateControl:
|
|
// Opening delimiters go on our stack so that we can hopefully
|
|
// match them with closing delimiters later.
|
|
push(tok.Type)
|
|
case hclsyntax.TokenCBrace:
|
|
open := pop()
|
|
if open != hclsyntax.TokenOBrace {
|
|
return false
|
|
}
|
|
case hclsyntax.TokenCBrack:
|
|
open := pop()
|
|
if open != hclsyntax.TokenOBrack {
|
|
return false
|
|
}
|
|
case hclsyntax.TokenCParen:
|
|
open := pop()
|
|
if open != hclsyntax.TokenOParen {
|
|
return false
|
|
}
|
|
case hclsyntax.TokenCHeredoc:
|
|
open := pop()
|
|
if open != hclsyntax.TokenOHeredoc {
|
|
return false
|
|
}
|
|
case hclsyntax.TokenTemplateSeqEnd:
|
|
open := pop()
|
|
if open != hclsyntax.TokenTemplateInterp && open != hclsyntax.TokenTemplateControl {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we get here without returning early then all of the closing delimeters
|
|
// were matched by opening delimiters. If our stack still contains at least
|
|
// one opening bracket then it seems like the user is intending to type
|
|
// more.
|
|
return len(delimStack) != 0
|
|
}
|