Repository: martinohmann/hcl-rs
Pull request #130 (2022-11-09 21:35:08)

fix(parser): improve grammar for heredoc
Previously, heredocs could contain invalid HCL templates. Now these will fail to parse.


Patch:
diff --git a/src/parser/expr.rs b/src/parser/expr.rs
index f78b7c1..af7dc1a 100644
--- a/src/parser/expr.rs
+++ b/src/parser/expr.rs
@@ -218,7 +218,7 @@ fn traversal_operator(pair: Pair<Rule>) -> Result<TraversalOperator> {
 fn template_expr(pair: Pair<Rule>) -> TemplateExpr {
     match pair.as_rule() {
         Rule::QuotedStringTemplate => TemplateExpr::QuotedString(string(inner(pair))),
-        Rule::HeredocTemplate => TemplateExpr::Heredoc(heredoc(pair)),
+        Rule::Heredoc => TemplateExpr::Heredoc(heredoc(pair)),
         rule => unexpected_rule(rule),
     }
 }
@@ -246,10 +246,16 @@ fn heredoc(pair: Pair<Rule>) -> Heredoc {
         rule => unexpected_rule(rule),
     };
 
+    let delimiter = ident(pairs.next().unwrap());
+    let mut template = string(pairs.next().unwrap());
+
+    // Append the trailing newline here. This is easier than doing this in the grammar.
+    template.push('\n');
+
     Heredoc {
         strip,
-        delimiter: ident(pairs.next().unwrap()),
-        template: string(pairs.next().unwrap()),
+        delimiter,
+        template,
     }
 }
 
diff --git a/src/parser/grammar/hcl.pest b/src/parser/grammar/hcl.pest
index 3999847..e4575d5 100644
--- a/src/parser/grammar/hcl.pest
+++ b/src/parser/grammar/hcl.pest
@@ -58,23 +58,31 @@ ObjectItemIdent = _{ Identifier ~ ("=" | ":") ~ Expression }
 ObjectItemExpr  = _{ Expression ~ ("=" | ":") ~ Expression }
 
 // Template expressions
-TemplateExpr = { QuotedStringTemplate | HeredocTemplate }
+TemplateExpr = { QuotedStringTemplate | Heredoc }
 
 // Heredoc templates
-HeredocTemplate = ${
+Heredoc = ${
     HeredocIntro ~ PUSH(Identifier) ~ NEWLINE ~
-    HeredocContent ~
+    HeredocTemplate ~ NEWLINE ~
     SpaceOrTab* ~ POP
 }
-HeredocIntro        = _{ HeredocIntroIndent | HeredocIntroNormal }
-HeredocIntroIndent  =  { "<<-" }
-HeredocIntroNormal  =  { "<<" }
-HeredocContent      = @{ HeredocContentInner ~ NEWLINE }
-HeredocContentInner = { (!(NEWLINE ~ SpaceOrTab* ~ PEEK) ~ ANY)* }
+HeredocIntro       = _{ HeredocIntroIndent | HeredocIntroNormal }
+HeredocIntroIndent =  { "<<-" }
+HeredocIntroNormal =  { "<<" }
+
+HeredocTemplate = ${
+    (HeredocLiteral | TemplateInterpolation | TemplateDirective)*
+}
+HeredocLiteral    = @{ HeredocStringPart+ }
+HeredocStringPart =  {
+    "$${"
+    | "%%{"
+    | !("${" | "%{" | (NEWLINE ~ SpaceOrTab* ~ PEEK)) ~ ANY
+}
 
 // Quoted string templates
 QuotedStringTemplate      = ${ "\"" ~ QuotedStringTemplateInner ~ "\"" }
-QuotedStringTemplateInner =  {
+QuotedStringTemplateInner = ${
     (QuotedStringTemplateLiteral | TemplateInterpolation | TemplateDirective)*
 }
 QuotedStringTemplateLiteral = @{ StringPart+ }
@@ -82,7 +90,7 @@ QuotedStringTemplateLiteral = @{ StringPart+ }
 // String literals
 StringLit  = ${ "\"" ~ String ~ "\"" }
 String     = @{ StringPart* }
-StringPart = {
+StringPart =  {
     "$${"
     | "%%{"
     | !("\"" | "\\" | "${" | "%{") ~ ANY


Test patch:
diff --git a/src/parser/tests.rs b/src/parser/tests.rs
index b334121..683a600 100644
--- a/src/parser/tests.rs
+++ b/src/parser/tests.rs
@@ -181,10 +181,42 @@ fn parse_template() {
         tokens: [
             ExprTerm(0, 54, [
                 TemplateExpr(0, 54, [
-                    HeredocTemplate(0, 54, [
+                    Heredoc(0, 54, [
                         HeredocIntroNormal(0, 2),
                         Identifier(2, 9),
-                        HeredocContent(10, 47)
+                        HeredocTemplate(10, 46, [
+                            TemplateInterpolation(10, 16, [
+                                TemplateIExprStartNormal(10, 12),
+                                Expression(12, 15, [
+                                    ExprTerm(12, 15, [
+                                        Variable(12, 15)
+                                    ])
+                                ]),
+                                TemplateExprEndNormal(15, 16)
+                            ]),
+                            HeredocLiteral(16, 17),
+                            TemplateDirective(17, 38, [
+                                TemplateIf(17, 38, [
+                                    TemplateIfExpr(17, 30, [
+                                        TemplateDExprStartNormal(17, 19),
+                                        Expression(22, 26, [
+                                            ExprTerm(22, 26, [
+                                                Variable(22, 26)
+                                            ])
+                                        ]),
+                                        TemplateExprEndNormal(26, 27),
+                                        Template(27, 30, [
+                                            TemplateLiteral(27, 30)
+                                        ]),
+                                    ]),
+                                    TemplateEndIfExpr(30, 38, [
+                                        TemplateDExprStartNormal(30, 32),
+                                        TemplateExprEndNormal(37, 38),
+                                    ])
+                                ])
+                            ]),
+                            HeredocLiteral(38, 46)
+                        ])
                     ])
                 ])
             ])