From c6d0588cd24c1a7dcd722b3d45b97d82fb77d0c2 Mon Sep 17 00:00:00 2001 From: martinohmann Date: Thu, 13 Jul 2023 20:52:51 +0200 Subject: [PATCH] refactor(parser)!: switch to `hcl-edit` for parsing HCL (#267) BREAKING CHANGES: The parser will now return an error if it encounters a duplicate attribute in a HCL body. Furthermore, the `Message` variant of the `Error` type was changed from `Message { msg: String, location: Option }` to `Message(String)` and the `Location` type was removed from the `error` module. Use the `location()` method of the error wrapped by the new `Error::Parse` variant to get access to the location where the parser failed. The old `pest` parser was replaced by the `winnow`-based parser that is used by `hcl-edit`. There's only one HCL parser implementation that needs to be maintained now. The `hcl-edit` parser is more correct and also over 100% faster than the old `pest`-based implementation. As a nice side effect, this decreases the number of transitive dependencies for the `hcl-rs` crate as well. --- Cargo.lock | 137 ------- crates/hcl-rs/Cargo.toml | 7 +- crates/hcl-rs/src/error.rs | 68 +--- crates/hcl-rs/src/expr/edit.rs | 7 +- crates/hcl-rs/src/expr/mod.rs | 1 - crates/hcl-rs/src/lib.rs | 1 - crates/hcl-rs/src/parser/expr.rs | 293 -------------- crates/hcl-rs/src/parser/grammar/hcl.pest | 178 --------- crates/hcl-rs/src/parser/mod.rs | 63 +-- crates/hcl-rs/src/parser/structure.rs | 51 --- crates/hcl-rs/src/parser/template.rs | 162 -------- crates/hcl-rs/src/parser/tests.rs | 378 ------------------ crates/hcl-rs/src/structure/mod.rs | 1 - crates/hcl-rs/src/template/mod.rs | 1 - crates/hcl-rs/src/util.rs | 48 --- crates/hcl-rs/tests/de.rs | 5 +- .../tests/expressions/invalid-heredoc.t | 2 +- 17 files changed, 31 insertions(+), 1372 deletions(-) delete mode 100644 crates/hcl-rs/src/parser/expr.rs delete mode 100644 crates/hcl-rs/src/parser/grammar/hcl.pest delete mode 100644 crates/hcl-rs/src/parser/structure.rs delete mode 100644 crates/hcl-rs/src/parser/template.rs delete mode 100644 crates/hcl-rs/src/parser/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 828c2500..4ed1cfa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,15 +66,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "block-buffer" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" -dependencies = [ - "generic-array", -] - [[package]] name = "bumpalo" version = "3.11.1" @@ -152,15 +143,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" -[[package]] -name = "cpufeatures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" -dependencies = [ - "libc", -] - [[package]] name = "criterion" version = "0.5.1" @@ -240,16 +222,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - [[package]] name = "ctor" version = "0.1.26" @@ -266,16 +238,6 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" -[[package]] -name = "digest" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" -dependencies = [ - "block-buffer", - "crypto-common", -] - [[package]] name = "either" version = "1.8.0" @@ -315,16 +277,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "generic-array" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" -dependencies = [ - "typenum", - "version_check", -] - [[package]] name = "getrandom" version = "0.2.8" @@ -390,8 +342,6 @@ dependencies = [ "indexmap", "indoc", "itoa", - "pest", - "pest_derive", "pretty_assertions", "serde", "serde_json", @@ -563,50 +513,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "pest" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6e86fb9e7026527a0d46bc308b841d73170ef8f443e1807f6ef88526a816d4" -dependencies = [ - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96504449aa860c8dcde14f9fba5c58dc6658688ca1fe363589d6327b8662c603" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "798e0220d1111ae63d66cb66a5dcb3fc2d986d520b98e49e1852bfdb11d7c5e7" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "984298b75898e30a843e278a9f2452c31e349a073a0ce6fd950a12a74464e065" -dependencies = [ - "once_cell", - "pest", - "sha1", -] - [[package]] name = "plotters" version = "0.3.4" @@ -771,17 +677,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha1" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "smawk" version = "0.3.1" @@ -830,26 +725,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "thiserror" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tinytemplate" version = "1.2.1" @@ -860,18 +735,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "ucd-trie" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" - [[package]] name = "unicode-ident" version = "1.0.6" diff --git a/crates/hcl-rs/Cargo.toml b/crates/hcl-rs/Cargo.toml index 269f32cf..1e656c3e 100644 --- a/crates/hcl-rs/Cargo.toml +++ b/crates/hcl-rs/Cargo.toml @@ -31,16 +31,13 @@ path = "src/lib.rs" [features] default = [] -edit = ["dep:hcl-edit"] -perf = ["hcl-edit?/perf", "hcl-primitives/perf"] +perf = ["hcl-edit/perf", "hcl-primitives/perf"] [dependencies] indexmap = { version = "2.0.0", features = ["serde"] } itoa = "1.0.8" -hcl-edit = { version = "0.7.0", path = "../hcl-edit", optional = true } +hcl-edit = { version = "0.7.0", path = "../hcl-edit" } hcl-primitives = { version = "0.1.1", path = "../hcl-primitives", features = ["serde"] } -pest = "2.5.2" -pest_derive = "2.5.2" serde = { version = "1.0.156", features = ["derive"] } vecmap-rs = { version = "0.1.11", features = ["serde"] } diff --git a/crates/hcl-rs/src/error.rs b/crates/hcl-rs/src/error.rs index 8324fe12..c7b17c6b 100644 --- a/crates/hcl-rs/src/error.rs +++ b/crates/hcl-rs/src/error.rs @@ -1,7 +1,6 @@ //! The `Error` and `Result` types used by this crate. +use crate::edit::parser; use crate::eval; -use crate::parser::Rule; -use pest::{error::LineColLocation, Span}; use serde::{de, ser}; use std::fmt::{self, Display}; use std::io; @@ -14,13 +13,8 @@ pub type Result = std::result::Result; #[derive(Debug)] #[non_exhaustive] pub enum Error { - /// Represents a generic error message with optional location. - Message { - /// The error message. - msg: String, - /// An optional location context where the error happened in the input. - location: Option, - }, + /// Represents a generic error message. + Message(String), /// Represents the error emitted when the `Deserializer` hits an unexpected end of input. Eof, /// Represents an error that resulted from invalid UTF8 input. @@ -37,6 +31,8 @@ pub enum Error { InvalidIdentifier(String), /// Represents errors during expression evaluation. Eval(eval::Error), + /// Represents errors while parsing HCL. + Parse(parser::Error), } impl Error { @@ -44,18 +40,7 @@ impl Error { where T: Display, { - Error::Message { - msg: msg.to_string(), - location: None, - } - } - - /// Returns the `Location` in the input where the error happened, if available. - pub fn location(&self) -> Option<&Location> { - match self { - Error::Message { location, .. } => location.as_ref(), - _ => None, - } + Error::Message(msg.to_string()) } } @@ -63,20 +48,16 @@ impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::Eof => write!(f, "unexpected end of input"), - Error::Io(err) => Display::fmt(err, f), - Error::Utf8(err) => Display::fmt(err, f), - Error::Message { msg, location } => match location { - Some(loc) => { - write!(f, "{msg} in line {}, col {}", loc.line, loc.col) - } - None => write!(f, "{msg}"), - }, + Error::Io(err) => write!(f, "{err}"), + Error::Utf8(err) => write!(f, "{err}"), + Error::Message(msg) => write!(f, "{msg}"), Error::InvalidEscape(c) => write!(f, "invalid escape sequence '\\{c}'"), Error::InvalidUnicodeCodePoint(u) => { write!(f, "invalid unicode code point '\\u{u}'") } Error::InvalidIdentifier(ident) => write!(f, "invalid identifier `{ident}`"), Error::Eval(err) => write!(f, "eval error: {err}"), + Error::Parse(err) => write!(f, "{err}"), } } } @@ -93,16 +74,9 @@ impl From for Error { } } -impl From> for Error { - fn from(err: pest::error::Error) -> Self { - let (line, col) = match err.line_col { - LineColLocation::Pos((l, c)) | LineColLocation::Span((l, c), (_, _)) => (l, c), - }; - - Error::Message { - msg: err.to_string(), - location: Some(Location { line, col }), - } +impl From for Error { + fn from(err: parser::Error) -> Self { + Error::Parse(err) } } @@ -125,19 +99,3 @@ impl de::Error for Error { Error::new(msg) } } - -/// One-based line and column at which the error was detected. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Location { - /// The one-based line number of the error. - pub line: usize, - /// The one-based column number of the error. - pub col: usize, -} - -impl From> for Location { - fn from(span: Span<'_>) -> Self { - let (line, col) = span.start_pos().line_col(); - Location { line, col } - } -} diff --git a/crates/hcl-rs/src/expr/edit.rs b/crates/hcl-rs/src/expr/edit.rs index cf14b276..958cb4bf 100644 --- a/crates/hcl-rs/src/expr/edit.rs +++ b/crates/hcl-rs/src/expr/edit.rs @@ -193,10 +193,9 @@ impl From for TemplateExpr { impl From for Heredoc { fn from(value: template::HeredocTemplate) -> Self { - let strip = match value.indent() { - Some(0) | None => HeredocStripMode::None, - Some(_) => HeredocStripMode::Indent, - }; + let strip = value + .indent() + .map_or(HeredocStripMode::None, |_| HeredocStripMode::Indent); Heredoc { delimiter: value.delimiter.into(), diff --git a/crates/hcl-rs/src/expr/mod.rs b/crates/hcl-rs/src/expr/mod.rs index 57492ad0..723684c1 100644 --- a/crates/hcl-rs/src/expr/mod.rs +++ b/crates/hcl-rs/src/expr/mod.rs @@ -5,7 +5,6 @@ mod conditional; pub(crate) mod de; -#[cfg(feature = "edit")] mod edit; mod for_expr; mod func_call; diff --git a/crates/hcl-rs/src/lib.rs b/crates/hcl-rs/src/lib.rs index e77bf0c6..8a4565d3 100644 --- a/crates/hcl-rs/src/lib.rs +++ b/crates/hcl-rs/src/lib.rs @@ -41,7 +41,6 @@ mod tests; mod util; pub mod value; -#[cfg(feature = "edit")] pub use hcl_edit as edit; // Re-exported for convenience. diff --git a/crates/hcl-rs/src/parser/expr.rs b/crates/hcl-rs/src/parser/expr.rs deleted file mode 100644 index 9efa0f5a..00000000 --- a/crates/hcl-rs/src/parser/expr.rs +++ /dev/null @@ -1,293 +0,0 @@ -use super::*; -use crate::{ - expr::{ - BinaryOp, Conditional, Expression, ForExpr, FuncCall, FuncCallBuilder, Heredoc, - HeredocStripMode, Object, ObjectKey, Operation, TemplateExpr, Traversal, TraversalOperator, - UnaryOp, UnaryOperator, Variable, - }, - util::dedent, -}; - -pub fn expression(pair: Pair) -> Result { - let pairs = pair.into_inner(); - let (expr, pairs) = unary_op(pairs)?; - let (expr, pairs) = binary_op(expr, pairs)?; - conditional(expr, pairs) -} - -fn unary_op(mut pairs: Pairs) -> Result<(Expression, Pairs)> { - let pair = pairs.next().unwrap(); - - let expr = match pair.as_rule() { - Rule::UnaryOperator => { - let operator = from_str(pair); - let expr = expr_term(pairs.next().unwrap())?; - - match (operator, expr) { - (UnaryOperator::Neg, Expression::Number(num)) => Expression::Number(-num), - (operator, expr) => Expression::from(Operation::Unary(UnaryOp { operator, expr })), - } - } - _ => expr_term(pair)?, - }; - - Ok((expr, pairs)) -} - -fn binary_op(expr: Expression, mut pairs: Pairs) -> Result<(Expression, Pairs)> { - let expr = match pairs.peek() { - Some(pair) => match pair.as_rule() { - Rule::BinaryOperator => Expression::from(Operation::Binary(BinaryOp { - lhs_expr: expr, - operator: from_str(pairs.next().unwrap()), - rhs_expr: expression(pairs.next().unwrap())?, - })), - _ => expr, - }, - None => expr, - }; - - Ok((expr, pairs)) -} - -fn conditional(expr: Expression, mut pairs: Pairs) -> Result { - let expr = match pairs.next() { - Some(pair) => Expression::from(Conditional { - cond_expr: expr, - true_expr: expression(pair)?, - false_expr: expression(pairs.next().unwrap())?, - }), - None => expr, - }; - - Ok(expr) -} - -fn expressions(pair: Pair) -> Result> { - pair.into_inner().map(expression).collect() -} - -fn expr_term(pair: Pair) -> Result { - let mut pairs = pair.into_inner(); - let pair = pairs.next().unwrap(); - - let expr = match pair.as_rule() { - Rule::BooleanLit => Expression::Bool(from_str(pair)), - Rule::Float => { - Number::from_f64(from_str::(pair)).map_or(Expression::Null, Expression::Number) - } - Rule::Int => Expression::from(from_str::(pair)), - Rule::NullLit => Expression::Null, - Rule::StringLit => unescape_string(inner(pair)).map(Expression::String)?, - Rule::TemplateExpr => Expression::TemplateExpr(Box::new(template_expr(inner(pair)))), - Rule::Tuple => expressions(pair).map(Expression::Array)?, - Rule::Object => object(pair).map(Expression::Object)?, - Rule::Variable => Expression::Variable(Variable::from(ident(pair))), - Rule::FunctionCall => Expression::FuncCall(Box::new(func_call(pair)?)), - Rule::Parenthesis => Expression::Parenthesis(Box::new(expression(inner(pair))?)), - Rule::ForExpr => Expression::from(for_expr(inner(pair))?), - rule => unexpected_rule(rule), - }; - - traversal(expr, pairs) -} - -fn traversal(expr: Expression, pairs: Pairs) -> Result { - let operators = pairs - .map(traversal_operator) - .collect::>>()?; - - if operators.is_empty() { - Ok(expr) - } else { - Ok(Expression::from(Traversal { expr, operators })) - } -} - -fn for_expr(pair: Pair) -> Result { - match pair.as_rule() { - Rule::ForTupleExpr => for_list_expr(pair), - Rule::ForObjectExpr => for_object_expr(pair), - rule => unexpected_rule(rule), - } -} - -fn for_list_expr(pair: Pair) -> Result { - let mut pairs = pair.into_inner(); - let intro = for_intro(pairs.next().unwrap())?; - let value_expr = expression(pairs.next().unwrap())?; - let cond_expr = match pairs.next() { - Some(pair) => Some(expression(inner(pair))?), - None => None, - }; - - Ok(ForExpr { - key_var: intro.key_var, - value_var: intro.value_var, - collection_expr: intro.collection_expr, - key_expr: None, - value_expr, - grouping: false, - cond_expr, - }) -} - -fn for_object_expr(pair: Pair) -> Result { - let mut pairs = pair.into_inner(); - let intro = for_intro(pairs.next().unwrap())?; - let key_expr = expression(pairs.next().unwrap())?; - let value_expr = expression(pairs.next().unwrap())?; - - let (grouping, cond_expr) = match (pairs.next(), pairs.next()) { - (Some(_), Some(pair)) => (true, Some(expression(inner(pair))?)), - (Some(pair), None) => match pair.as_rule() { - Rule::ValueGrouping => (true, None), - Rule::ForCond => (false, Some(expression(inner(pair))?)), - rule => unexpected_rule(rule), - }, - (_, _) => (false, None), - }; - - Ok(ForExpr { - key_var: intro.key_var, - value_var: intro.value_var, - collection_expr: intro.collection_expr, - key_expr: Some(key_expr), - value_expr, - grouping, - cond_expr, - }) -} - -struct ForIntro { - key_var: Option, - value_var: Identifier, - collection_expr: Expression, -} - -fn for_intro(pair: Pair) -> Result { - let mut pairs = pair.into_inner(); - let mut value_var = Some(ident(pairs.next().unwrap())); - let mut expr = pairs.next().unwrap(); - - // If there are two identifiers, the first one is the key and the second one the value. - let key_var = match expr.as_rule() { - Rule::Identifier => { - let key = value_var.replace(ident(expr)); - expr = pairs.next().unwrap(); - key - } - _ => None, - }; - - Ok(ForIntro { - key_var, - value_var: value_var.take().unwrap(), - collection_expr: expression(expr)?, - }) -} - -fn func_call(pair: Pair) -> Result { - let mut pairs = pair.into_inner(); - let builder = FuncCall::builder(ident(pairs.next().unwrap())); - let mut args = pairs.next().unwrap().into_inner(); - - args.try_fold(builder, |builder, pair| match pair.as_rule() { - Rule::ExpandFinal => Ok(builder.expand_final(true)), - _ => Ok(builder.arg(expression(pair)?)), - }) - .map(FuncCallBuilder::build) -} - -fn traversal_operator(pair: Pair) -> Result { - let operator = match pair.as_rule() { - Rule::AttrSplat => TraversalOperator::AttrSplat, - Rule::FullSplat => TraversalOperator::FullSplat, - Rule::GetAttr => TraversalOperator::GetAttr(ident(inner(pair))), - Rule::Index => { - let pair = inner(pair); - - match pair.as_rule() { - Rule::LegacyIndex => TraversalOperator::LegacyIndex(from_str::(inner(pair))), - _ => TraversalOperator::Index(expression(pair)?), - } - } - rule => unexpected_rule(rule), - }; - - Ok(operator) -} - -fn template_expr(pair: Pair) -> TemplateExpr { - match pair.as_rule() { - Rule::QuotedStringTemplate => TemplateExpr::QuotedString(string(inner(pair))), - Rule::Heredoc => TemplateExpr::Heredoc(heredoc(pair)), - rule => unexpected_rule(rule), - } -} - -fn object(pair: Pair) -> Result> { - ObjectIter::new(pair) - .map(|(k, v)| Ok((object_key(k)?, expression(v)?))) - .collect() -} - -fn object_key(pair: Pair) -> Result { - match pair.as_rule() { - Rule::Identifier => Ok(ObjectKey::Identifier(ident(pair))), - _ => expression(pair).map(ObjectKey::Expression), - } -} - -fn heredoc(pair: Pair) -> Heredoc { - let mut pairs = pair.into_inner(); - let intro = pairs.next().unwrap(); - - let strip = match intro.as_rule() { - Rule::HeredocIntroNormal => HeredocStripMode::None, - Rule::HeredocIntroIndent => HeredocStripMode::Indent, - rule => unexpected_rule(rule), - }; - - let delimiter = ident(pairs.next().unwrap()); - - let template = pairs.next().unwrap(); - - let mut template = match strip { - HeredocStripMode::None => string(template), - HeredocStripMode::Indent => dedent(template.as_str()).to_string(), - }; - - // Append the trailing newline here. This is easier than doing this in the grammar. - template.push('\n'); - - Heredoc { - delimiter, - template, - strip, - } -} - -struct ObjectIter<'a> { - inner: Pairs<'a, Rule>, -} - -impl<'a> ObjectIter<'a> { - fn new(pair: Pair<'a, Rule>) -> Self { - ObjectIter { - inner: pair.into_inner(), - } - } -} - -impl<'a> Iterator for ObjectIter<'a> { - type Item = (Pair<'a, Rule>, Pair<'a, Rule>); - - fn next(&mut self) -> Option { - match (self.inner.next(), self.inner.next()) { - (Some(k), Some(v)) => Some((k, v)), - (Some(k), None) => panic!("missing value for key: {k}"), - (_, _) => None, - } - } -} diff --git a/crates/hcl-rs/src/parser/grammar/hcl.pest b/crates/hcl-rs/src/parser/grammar/hcl.pest deleted file mode 100644 index 2a45aaf6..00000000 --- a/crates/hcl-rs/src/parser/grammar/hcl.pest +++ /dev/null @@ -1,178 +0,0 @@ -// 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 | Block)* } -Attribute = { 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 = _{ ("#" | "//") ~ (!EoInlineComment ~ ANY)* } -BlockComment = _{ "/*" ~ (!"*/" ~ ANY)* ~ "*/" } -EoInlineComment = _{ NEWLINE | EOI } - -// 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 } diff --git a/crates/hcl-rs/src/parser/mod.rs b/crates/hcl-rs/src/parser/mod.rs index e95bde10..2a7f2ec4 100644 --- a/crates/hcl-rs/src/parser/mod.rs +++ b/crates/hcl-rs/src/parser/mod.rs @@ -1,25 +1,7 @@ -mod expr; -mod structure; -mod template; -#[cfg(test)] -mod tests; - -use self::{expr::expression, structure::body, template::template}; -use crate::{ - expr::Expression, structure::Body, template::Template, util::unescape, Identifier, Number, - Result, -}; -use hcl_primitives::template::unescape_markers; -use pest::{ - iterators::{Pair, Pairs}, - Parser as _, -}; -use pest_derive::Parser; -use std::str::FromStr; - -#[derive(Parser)] -#[grammar = "parser/grammar/hcl.pest"] -struct HclParser; +use crate::edit; +use crate::structure::Body; +use crate::template::Template; +use crate::Result; /// Parse a `hcl::Body` from a `&str`. /// @@ -63,40 +45,11 @@ struct HclParser; /// /// This function fails with an error if the `input` cannot be parsed as HCL. pub fn parse(input: &str) -> Result { - let pair = HclParser::parse(Rule::Hcl, input)?.next().unwrap(); - body(pair) + let body: edit::structure::Body = input.parse()?; + Ok(body.into()) } pub fn parse_template(input: &str) -> Result