hcl/hclsyntax: correctly handle %{ sequence escapes
In early prototyping the template control sequence introducer was specified as !{, but that changed to %{ along the way because it seemed more intuitive and less likely to collide with literal strings. However, the parser's string literal handling still had remnants of the old syntax, causing strange quirks in parsing strings that contained exclamation points. Now we correctly expect %{ as the control sequence introducer, %%{ as its escape sequence, and additionally fix a bug where previously template sequence introduction characters at the end of a string literal would be silently dropped due to them representing an unterminated escape sequence. This fixes #3.
This commit is contained in:
parent
0daeda39ff
commit
83451bb547
@ -1531,7 +1531,7 @@ Character:
|
||||
|
||||
var detail string
|
||||
switch {
|
||||
case len(ch) == 1 && (ch[0] == '$' || ch[0] == '!'):
|
||||
case len(ch) == 1 && (ch[0] == '$' || ch[0] == '%'):
|
||||
detail = fmt.Sprintf(
|
||||
"The characters \"\\%s\" do not form a recognized escape sequence. To escape a \"%s{\" template sequence, use \"%s%s{\".",
|
||||
ch, ch, ch, ch,
|
||||
@ -1562,7 +1562,7 @@ Character:
|
||||
esc = esc[:0]
|
||||
continue Character
|
||||
|
||||
case '$', '!':
|
||||
case '$', '%':
|
||||
switch len(esc) {
|
||||
case 1:
|
||||
if len(ch) == 1 && ch[0] == esc[0] {
|
||||
@ -1602,8 +1602,8 @@ Character:
|
||||
case '$':
|
||||
esc = append(esc, '$')
|
||||
continue Character
|
||||
case '!':
|
||||
esc = append(esc, '!')
|
||||
case '%':
|
||||
esc = append(esc, '%')
|
||||
continue Character
|
||||
}
|
||||
}
|
||||
@ -1611,6 +1611,42 @@ Character:
|
||||
}
|
||||
}
|
||||
|
||||
// if we still have an outstanding "esc" when we fall out here then
|
||||
// the literal ended with an unterminated escape sequence, which we
|
||||
// must now deal with.
|
||||
if len(esc) > 0 {
|
||||
if esc[0] == '\\' {
|
||||
// An incomplete backslash sequence is an error, since it suggests
|
||||
// that e.g. the user started writing a \uXXXX sequence but didn't
|
||||
// provide enough hex digits.
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid escape sequence",
|
||||
Detail: fmt.Sprintf("The characters %q do not form a recognized escape sequence.", esc),
|
||||
Subject: &hcl.Range{
|
||||
Filename: tok.Range.Filename,
|
||||
Start: hcl.Pos{
|
||||
Line: pos.Line,
|
||||
Column: pos.Column,
|
||||
Byte: pos.Byte,
|
||||
},
|
||||
End: hcl.Pos{
|
||||
Line: pos.Line,
|
||||
Column: pos.Column + len(esc),
|
||||
Byte: pos.Byte + len(esc),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
// This might also be an incomplete $${ or %%{ escape sequence, but
|
||||
// that's treated as a literal rather than an error since those only
|
||||
// count as escape sequences when all three characters are present.
|
||||
|
||||
ret = append(ret, esc...)
|
||||
esc = nil
|
||||
}
|
||||
|
||||
return string(ret), diags
|
||||
}
|
||||
|
||||
|
@ -435,7 +435,7 @@ Token:
|
||||
})
|
||||
|
||||
case TokenTemplateControl:
|
||||
// if the opener is !{~ then we want to eat any trailing whitespace
|
||||
// if the opener is %{~ then we want to eat any trailing whitespace
|
||||
// in the preceding literal token, assuming it is indeed a literal
|
||||
// token.
|
||||
if canTrimPrev && len(next.Bytes) == 3 && next.Bytes[2] == '~' && len(parts) > 0 {
|
||||
@ -452,7 +452,7 @@ Token:
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid template directive",
|
||||
Detail: "A template directive keyword (\"if\", \"for\", etc) is expected at the beginning of a !{ sequence.",
|
||||
Detail: "A template directive keyword (\"if\", \"for\", etc) is expected at the beginning of a %{ sequence.",
|
||||
Subject: &kw.Range,
|
||||
Context: hcl.RangeBetween(next.Range, kw.Range).Ptr(),
|
||||
})
|
||||
|
@ -465,6 +465,256 @@ block "valid" {}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"a = \"hello $${true}\"\n",
|
||||
0,
|
||||
&Body{
|
||||
Attributes: Attributes{
|
||||
"a": {
|
||||
Name: "a",
|
||||
Expr: &TemplateExpr{
|
||||
Parts: []Expression{
|
||||
&LiteralValueExpr{
|
||||
Val: cty.StringVal("hello ${true}"),
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||
End: hcl.Pos{Line: 1, Column: 20, Byte: 19},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
|
||||
End: hcl.Pos{Line: 1, Column: 21, Byte: 20},
|
||||
},
|
||||
},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 21, Byte: 20},
|
||||
},
|
||||
NameRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
|
||||
},
|
||||
EqualsRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 3, Byte: 2},
|
||||
End: hcl.Pos{Line: 1, Column: 4, Byte: 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
Blocks: Blocks{},
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 21},
|
||||
},
|
||||
EndRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 2, Column: 1, Byte: 21},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 21},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"a = \"hello %%{true}\"\n",
|
||||
0,
|
||||
&Body{
|
||||
Attributes: Attributes{
|
||||
"a": {
|
||||
Name: "a",
|
||||
Expr: &TemplateExpr{
|
||||
Parts: []Expression{
|
||||
&LiteralValueExpr{
|
||||
Val: cty.StringVal("hello %{true}"),
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||
End: hcl.Pos{Line: 1, Column: 20, Byte: 19},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
|
||||
End: hcl.Pos{Line: 1, Column: 21, Byte: 20},
|
||||
},
|
||||
},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 21, Byte: 20},
|
||||
},
|
||||
NameRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
|
||||
},
|
||||
EqualsRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 3, Byte: 2},
|
||||
End: hcl.Pos{Line: 1, Column: 4, Byte: 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
Blocks: Blocks{},
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 21},
|
||||
},
|
||||
EndRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 2, Column: 1, Byte: 21},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 21},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"a = \"hello $$\"\n",
|
||||
0,
|
||||
&Body{
|
||||
Attributes: Attributes{
|
||||
"a": {
|
||||
Name: "a",
|
||||
Expr: &TemplateExpr{
|
||||
Parts: []Expression{
|
||||
&LiteralValueExpr{
|
||||
Val: cty.StringVal("hello $$"),
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||
End: hcl.Pos{Line: 1, Column: 14, Byte: 13},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
|
||||
End: hcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||
},
|
||||
},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||
},
|
||||
NameRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
|
||||
},
|
||||
EqualsRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 3, Byte: 2},
|
||||
End: hcl.Pos{Line: 1, Column: 4, Byte: 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
Blocks: Blocks{},
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 15},
|
||||
},
|
||||
EndRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 2, Column: 1, Byte: 15},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 15},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"a = \"hello %%\"\n",
|
||||
0,
|
||||
&Body{
|
||||
Attributes: Attributes{
|
||||
"a": {
|
||||
Name: "a",
|
||||
Expr: &TemplateExpr{
|
||||
Parts: []Expression{
|
||||
&LiteralValueExpr{
|
||||
Val: cty.StringVal("hello %%"),
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||
End: hcl.Pos{Line: 1, Column: 14, Byte: 13},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
|
||||
End: hcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||
},
|
||||
},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 15, Byte: 14},
|
||||
},
|
||||
NameRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
|
||||
},
|
||||
EqualsRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 3, Byte: 2},
|
||||
End: hcl.Pos{Line: 1, Column: 4, Byte: 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
Blocks: Blocks{},
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 15},
|
||||
},
|
||||
EndRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 2, Column: 1, Byte: 15},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 15},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"a = \"hello!\"\n",
|
||||
0,
|
||||
&Body{
|
||||
Attributes: Attributes{
|
||||
"a": {
|
||||
Name: "a",
|
||||
Expr: &TemplateExpr{
|
||||
Parts: []Expression{
|
||||
&LiteralValueExpr{
|
||||
Val: cty.StringVal("hello!"),
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 6, Byte: 5},
|
||||
End: hcl.Pos{Line: 1, Column: 12, Byte: 11},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 5, Byte: 4},
|
||||
End: hcl.Pos{Line: 1, Column: 13, Byte: 12},
|
||||
},
|
||||
},
|
||||
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 13, Byte: 12},
|
||||
},
|
||||
NameRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 1, Column: 2, Byte: 1},
|
||||
},
|
||||
EqualsRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 3, Byte: 2},
|
||||
End: hcl.Pos{Line: 1, Column: 4, Byte: 3},
|
||||
},
|
||||
},
|
||||
},
|
||||
Blocks: Blocks{},
|
||||
SrcRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 1, Column: 1, Byte: 0},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 13},
|
||||
},
|
||||
EndRange: hcl.Range{
|
||||
Start: hcl.Pos{Line: 2, Column: 1, Byte: 13},
|
||||
End: hcl.Pos{Line: 2, Column: 1, Byte: 13},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"a = foo.bar\n",
|
||||
0,
|
||||
|
Loading…
Reference in New Issue
Block a user