mod builders;
mod depth;
mod guard;
mod many;
use std::sync::Arc;
use pest::{Parser, error::InputLocation};
use crate::{
ast::{SourceSpan, Statement},
diagnostic::DiagnosticReport,
error::ParserError,
flagger,
};
use self::pest_impl::{GqlParser, Rule};
mod pest_impl {
#![allow(missing_docs)]
#[derive(pest_derive::Parser)]
#[grammar = "parser/grammar.pest"]
pub(crate) struct GqlParser;
}
pub(crate) const MAX_NESTING_DEPTH: u32 = guard::MAX_NESTING_DEPTH;
#[tracing::instrument(name = "selene.gql.parse", skip(source), fields(source_len = source.len()))]
pub fn parse(source: &str) -> Result<Statement, ParserError> {
guard::validate(source)?;
stacker::maybe_grow(PARSE_STACK_RED_ZONE, PARSE_STACK_SEGMENT, || {
let mut pairs = GqlParser::parse(Rule::gql_program, source)
.map_err(|error| pest_error(source, error))?;
let program_pair = pairs.next().ok_or_else(ParserError::empty_program)?;
let statement = builders::build_statement(program_pair)?;
depth::reject_excessive_expr_depth(&statement)?;
flagger::flag(&statement)?;
Ok(statement)
})
}
const PARSE_STACK_RED_ZONE: usize = 24 * 1024 * 1024;
const PARSE_STACK_SEGMENT: usize = 32 * 1024 * 1024;
pub fn parse_with_source(
source: Arc<str>,
label: impl Into<String>,
) -> Result<Statement, DiagnosticReport> {
parse(&source).map_err(|error| DiagnosticReport::new(error, source, label))
}
pub use many::parse_many;
fn pest_error(source: &str, error: pest::error::Error<Rule>) -> ParserError {
let span = match error.location {
InputLocation::Pos(offset) => point_span(offset),
InputLocation::Span((start, end)) => SourceSpan::new(to_u32(start), to_u32(end - start)),
};
let message = if source.is_empty() {
"empty GQL program".to_owned()
} else {
error.variant.message().to_string()
};
ParserError::syntax(
message,
span,
Some("check GQL syntax near the highlighted span".into()),
)
}
fn point_span(offset: usize) -> SourceSpan {
SourceSpan::new(to_u32(offset), 0)
}
fn to_u32(value: usize) -> u32 {
u32::try_from(value).unwrap_or(u32::MAX)
}
#[cfg(test)]
mod tests;