use super::hcl::ast::error::Error as AstError;
pub mod ast;
mod depth;
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
pub enum Error {
#[error(transparent)]
Pest(#[from] Box<pest::error::Error<Rule>>),
#[error("File is too large to parse: {len} bytes, limit is {max}")]
#[diagnostic(code(hcl::too_large))]
TooLarge { len: usize, max: usize },
#[error("File nests too deeply to parse: {depth} levels, limit is {max}")]
#[diagnostic(
code(hcl::too_deep),
help(
"Brackets and chained operators each make the parser recurse, and a deep enough file overflows the stack. Split the expression."
)
)]
TooDeep { depth: usize, max: usize },
}
impl From<pest::error::Error<Rule>> for Error {
fn from(value: pest::error::Error<Rule>) -> Self {
Self::Pest(Box::new(value))
}
}
type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(pest_derive::Parser)]
#[grammar = "parsers/hcl/hcl.pest"]
struct HclParser;
type Pair<'i> = pest::iterators::Pair<'i, Rule>;
type Pairs<'i> = pest::iterators::Pairs<'i, Rule>;
const MAX_INPUT_BYTES: usize = 1024 * 1024;
pub fn parse(input: &str) -> Result<(ast::Hcl<'_>, Vec<AstError>)> {
use pest::Parser;
if input.len() > MAX_INPUT_BYTES {
return Err(Error::TooLarge {
len: input.len(),
max: MAX_INPUT_BYTES,
});
}
depth::check(input)?;
let mut pairs = HclParser::parse(Rule::Hcl, input)?;
Ok(ast::process(pairs.next().unwrap()))
}
#[cfg(test)]
mod tests {
use super::{Error, parse};
#[test]
fn parse_refuses_a_file_that_would_overflow_the_stack() {
for input in [
format!("a = {}1{}\n", "[".repeat(2_000), "]".repeat(2_000)),
format!("a = 1{}\n", "-1".repeat(20_000)),
format!("{}\n{}", "b {".repeat(2_000), "}".repeat(2_000)),
] {
assert!(
matches!(parse(&input), Err(Error::TooDeep { .. })),
"a {} byte input was handed to pest",
input.len()
);
}
}
#[test]
fn parse_still_reads_the_sample_the_repository_ships() {
let input = std::fs::read_to_string("main.sample.tf").unwrap();
let (ast, errors) = parse(&input).unwrap();
assert!(errors.is_empty(), "{errors:?}");
assert!(ast.get_block("terraform", None).is_some());
}
}