Skip to content

Commit

Permalink
refactor(parser)!: switch to hcl-edit for parsing HCL (#267)
Browse files Browse the repository at this point in the history
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<Location> }` 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.
  • Loading branch information
martinohmann committed Jul 13, 2023
1 parent 4f6600e commit c6d0588
Show file tree
Hide file tree
Showing 17 changed files with 31 additions and 1,372 deletions.
137 changes: 0 additions & 137 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions crates/hcl-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }

Expand Down
68 changes: 13 additions & 55 deletions crates/hcl-rs/src/error.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -14,13 +13,8 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
#[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<Location>,
},
/// 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.
Expand All @@ -37,46 +31,33 @@ pub enum Error {
InvalidIdentifier(String),
/// Represents errors during expression evaluation.
Eval(eval::Error),
/// Represents errors while parsing HCL.
Parse(parser::Error),
}

impl Error {
pub(crate) fn new<T>(msg: T) -> 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())
}
}

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}"),
}
}
}
Expand All @@ -93,16 +74,9 @@ impl From<Utf8Error> for Error {
}
}

impl From<pest::error::Error<Rule>> for Error {
fn from(err: pest::error::Error<Rule>) -> 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<parser::Error> for Error {
fn from(err: parser::Error) -> Self {
Error::Parse(err)
}
}

Expand All @@ -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<Span<'_>> for Location {
fn from(span: Span<'_>) -> Self {
let (line, col) = span.start_pos().line_col();
Location { line, col }
}
}
7 changes: 3 additions & 4 deletions crates/hcl-rs/src/expr/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,9 @@ impl From<template::StringTemplate> for TemplateExpr {

impl From<template::HeredocTemplate> 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(),
Expand Down
1 change: 0 additions & 1 deletion crates/hcl-rs/src/expr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

mod conditional;
pub(crate) mod de;
#[cfg(feature = "edit")]
mod edit;
mod for_expr;
mod func_call;
Expand Down
1 change: 0 additions & 1 deletion crates/hcl-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ mod tests;
mod util;
pub mod value;

#[cfg(feature = "edit")]
pub use hcl_edit as edit;

// Re-exported for convenience.
Expand Down
Loading

0 comments on commit c6d0588

Please sign in to comment.