// HCL spec (https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md)
// The top-level scope of an HCL file
Hcl = _{ SOI ~ Body ~ EOI }
// Structural elements
Body = { (Attribute | AttributeError | Block)* }
Attribute = { InlineHint? ~ Identifier ~ "=" ~ Expression }
AttributeError = { InlineHint{2,} ~ Identifier ~ "=" ~ Expression }
Block = { Identifier ~ (StringLit | Identifier)* ~ BlockBody }
BlockBody = { "{" ~ Body ~ "}" }
// Expressions
Expression = {
UnaryOperator? ~
ExprTerm ~
(BinaryOperator ~ Expression)? ~
("?" ~ Expression ~ ":" ~ Expression)?
}
ExprTerm = {
(Value | TemplateExpr | FunctionCall | Variable | ForExpr | Parenthesis) ~
(Splat | GetAttr | Index)*
}
Parenthesis = { "(" ~ Expression ~ ")" }
// Values
Value = _{ LiteralValue | CollectionValue }
// Literal values
LiteralValue = _{ StringLit | NumericLit | BooleanLit | NullLit }
// Identifiers
Identifier = @{ IdentFirstChar ~ IdentChar* }
IdentChar = _{ Letter | Decimal | "-" | "_" }
IdentFirstChar = _{ Letter | "_" }
Letter = _{ 'a'..'z' | 'A'..'Z' }
// Booleans
BooleanLit = @{ Boolean ~ !Identifier }
Boolean = { "true" | "false" }
// Null
NullLit = @{ Null ~ !Identifier }
Null = { "null" }
// Numeric literals
NumericLit = _{ Float | Int }
Float = @{ Decimal+ ~ (("." ~ Decimal+ ~ (ExpMark ~ Decimal+)?) | (ExpMark ~ Decimal+)) }
Int = @{ Decimal+ }
ExpMark = { ("e" | "E") ~ ("+" | "-")? }
Decimal = { '0'..'9' }
// Collection values
CollectionValue = _{ Tuple | Object }
Tuple = { "[" ~ (Expression ~ ("," ~ Expression)* ~ ","?)? ~ "]" }
Object = { "{" ~ (ObjectItem ~ (","? ~ ObjectItem)* ~ ","?)? ~ "}" }
ObjectItem = _{ ObjectItemIdent | ObjectItemExpr }
ObjectItemIdent = _{ Identifier ~ ("=" | ":") ~ Expression }
ObjectItemExpr = _{ Expression ~ ("=" | ":") ~ Expression }
// Template expressions
TemplateExpr = { QuotedStringTemplate | Heredoc }
// Heredoc templates
Heredoc = ${
HeredocIntro ~ PUSH(Identifier) ~ NEWLINE ~
HeredocTemplate ~ NEWLINE ~
SpaceOrTab* ~ POP
}
HeredocIntro = _{ HeredocIntroIndent | HeredocIntroNormal }
HeredocIntroIndent = { "<<-" }
HeredocIntroNormal = { "<<" }
HeredocTemplate = ${
(HeredocLiteral | TemplateInterpolation | TemplateDirective)*
}
HeredocLiteral = @{ HeredocStringPart+ }
HeredocStringPart = {
"$${"
| "%%{"
| !("${" | "%{" | (NEWLINE ~ SpaceOrTab* ~ PEEK)) ~ ANY
}
// Quoted string templates
QuotedStringTemplate = ${ "\"" ~ QuotedStringTemplateInner ~ "\"" }
QuotedStringTemplateInner = ${
(QuotedStringTemplateLiteral | TemplateInterpolation | TemplateDirective)*
}
QuotedStringTemplateLiteral = @{ StringPart+ }
// String literals
StringLit = ${ "\"" ~ String ~ "\"" }
String = @{ StringPart* }
StringPart = {
"$${"
| "%%{"
| !("\"" | "\\" | "${" | "%{") ~ ANY
| "\\" ~ ("\"" | "\\" | "/" | "b" | "f" | "n" | "r" | "t")
| "\\" ~ ("u" ~ ASCII_HEX_DIGIT{4})
}
// Functions and function calls
FunctionCall = { Identifier ~ Arguments }
Arguments = { "(" ~ (Expression ~ ("," ~ Expression)* ~ ("," | ExpandFinal)?)? ~ ")" }
ExpandFinal = { "..." }
// For expressions
ForExpr = { ForTupleExpr | ForObjectExpr }
ForTupleExpr = { "[" ~ ForIntro ~ Expression ~ ForCond? ~ "]" }
ForObjectExpr = { "{" ~ ForIntro ~ Expression ~ "=>" ~ Expression ~ ValueGrouping? ~ ForCond? ~ "}" }
ForIntro = { "for" ~ Identifier ~ ("," ~ Identifier)? ~ "in" ~ Expression ~ ":" }
ForCond = { "if" ~ Expression }
ValueGrouping = { "..." }
// Variables
Variable = @{ Identifier }
// Index operator
Index = { ("[" ~ Expression ~ "]") | LegacyIndex }
LegacyIndex = ${ "." ~ Int }
// Attribute access operator
GetAttr = ${ "." ~ Identifier }
// Splat operators
Splat = _{ (AttrSplat ~ GetAttr*) | (FullSplat ~ (GetAttr | Index)*) }
AttrSplat = { ".*" }
FullSplat = { "[*]" }
// Unary and binary operators
UnaryOperator = { "-" | "!" }
BinaryOperator = { CompareOperator | ArithmeticOperator | LogicOperator }
CompareOperator = { "==" | "!=" | "<=" | ">=" | "<" | ">" }
ArithmeticOperator = { "+" | "-" | "*" | "/" | "%" }
LogicOperator = { "&&" | "||" }
// Comments
COMMENT = _{ InlineComment | BlockComment }
InlineComment = _{ ("#" | "//") ~ !(SpaceOrTab* ~ "@adnv") ~ (!EoInlineComment ~ ANY)* }
BlockComment = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" }
EoInlineComment = _{ NEWLINE | EOI }
InlineHint = ${ ("#" | "//") ~ SpaceOrTab* ~ "@adnv" ~ SpaceOrTab+ ~ (SourceInlineHint | InlineHintError) ~ SpaceOrTab* ~ NEWLINE }
SourceInlineHint = ${ "source" ~ SpaceOrTab+ ~ StringLit ~ SpaceOrTab+ ~ StringLit }
InlineHintError = ${ (!NEWLINE ~ ANY)* }
// Whitespace
WHITESPACE = _{ SpaceOrTab | NEWLINE }
SpaceOrTab = _{ " " | "\t" }
// Top-level rule for the template sub-language
HclTemplate = ${ SOI ~ Template ~ EOI }
Template = ${ (TemplateLiteral | TemplateInterpolation | TemplateDirective)* }
TemplateLiteral = @{ ("$${" | "%%{" | (!("${" | "%{") ~ ANY))+ }
TemplateInterpolation = !{ TemplateIExprStart ~ Expression ~ TemplateExprEnd }
TemplateDirective = { TemplateIf | TemplateFor }
// Interpolation expression start
TemplateIExprStart = _{ TemplateIExprStartStrip | TemplateIExprStartNormal }
TemplateIExprStartNormal = { "${" }
TemplateIExprStartStrip = { "${~" }
// Directive expression start
TemplateDExprStart = _{ TemplateDExprStartStrip | TemplateDExprStartNormal }
TemplateDExprStartNormal = { "%{" }
TemplateDExprStartStrip = { "%{~" }
// Interpolation or directive expression end
TemplateExprEnd = _{ TemplateExprEndNormal | TemplateExprEndStrip }
TemplateExprEndNormal = { "}" }
TemplateExprEndStrip = { "~}" }
// If directive
TemplateIf = ${ TemplateIfExpr ~ Template ~ (TemplateElseExpr ~ Template)? ~ TemplateEndIfExpr }
TemplateIfExpr = !{ TemplateDExprStart ~ "if" ~ Expression ~ TemplateExprEnd }
TemplateElseExpr = !{ TemplateDExprStart ~ "else" ~ TemplateExprEnd }
TemplateEndIfExpr = !{ TemplateDExprStart ~ "endif" ~ TemplateExprEnd }
// For directive
TemplateFor = ${ TemplateForExpr ~ Template ~ TemplateEndForExpr }
TemplateForExpr = !{ TemplateDExprStart ~ "for" ~ Identifier ~ ("," ~ Identifier)? ~ "in" ~ Expression ~ TemplateExprEnd }
TemplateEndForExpr = !{ TemplateDExprStart ~ "endfor" ~ TemplateExprEnd }