Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion bluejay-parser/benches/parse.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use bluejay_parser::ast::{
definition::{DefaultContext, DefinitionDocument},
executable::ExecutableDocument,
Parse,
};
use criterion::{criterion_group, criterion_main, Criterion};
Expand All @@ -13,7 +14,12 @@ fn parse(c: &mut Criterion) {

let s = std::fs::read_to_string("../data/kitchen_sink.graphql").unwrap();
c.bench_function("parse kitchen sink executable document", |b| {
b.iter(|| DefinitionDocument::<DefaultContext>::parse(black_box(s.as_str())))
b.iter(|| ExecutableDocument::parse(black_box(s.as_str())))
});

let s = std::fs::read_to_string("../data/large_executable.graphql").unwrap();
c.bench_function("parse large executable document", |b| {
b.iter(|| ExecutableDocument::parse(black_box(s.as_str())))
});
}

Expand Down
24 changes: 13 additions & 11 deletions bluejay-parser/src/ast/definition/definition_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,19 +158,21 @@ impl<'a, C: Context> Parse<'a> for DefinitionDocument<'a, C> {

impl<'a, C: Context> DefinitionDocument<'a, C> {
fn new() -> Self {
let mut type_definitions = Vec::with_capacity(64);
type_definitions.extend([
ObjectTypeDefinition::__schema().into(),
ObjectTypeDefinition::__type().into(),
ObjectTypeDefinition::__field().into(),
ObjectTypeDefinition::__input_value().into(),
ObjectTypeDefinition::__enum_value().into(),
ObjectTypeDefinition::__directive().into(),
EnumTypeDefinition::__type_kind().into(),
EnumTypeDefinition::__directive_location().into(),
]);
Self {
schema_definitions: Vec::new(),
directive_definitions: Vec::new(),
type_definitions: vec![
ObjectTypeDefinition::__schema().into(),
ObjectTypeDefinition::__type().into(),
ObjectTypeDefinition::__field().into(),
ObjectTypeDefinition::__input_value().into(),
ObjectTypeDefinition::__enum_value().into(),
ObjectTypeDefinition::__directive().into(),
EnumTypeDefinition::__type_kind().into(),
EnumTypeDefinition::__directive_location().into(),
],
directive_definitions: Vec::with_capacity(8),
type_definitions,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ impl<C: Context> From<DefinitionDocumentError<'_, C>> for Error {
.map(|definition| {
Annotation::new(
format!("Directive definition with name `@{name}`"),
definition.name_token().span().clone(),
*definition.name_token().span(),
)
})
.collect(),
Expand All @@ -101,7 +101,7 @@ impl<C: Context> From<DefinitionDocumentError<'_, C>> for Error {
.map(|rotd| {
Annotation::new(
format!("Root operation type definition for `{operation_type}`"),
rotd.name_token().span().clone(),
*rotd.name_token().span(),
)
})
.collect(),
Expand All @@ -115,7 +115,7 @@ impl<C: Context> From<DefinitionDocumentError<'_, C>> for Error {
.map(|definition| {
Annotation::new(
"Schema definition",
definition.schema_identifier_span().clone(),
*definition.schema_identifier_span(),
)
})
.collect(),
Expand All @@ -140,7 +140,7 @@ impl<C: Context> From<DefinitionDocumentError<'_, C>> for Error {
.map(|definition| {
Annotation::new(
format!("Type definition with name `{name}`"),
definition.name_token().unwrap().span().clone(),
*definition.name_token().unwrap().span(),
)
})
.collect(),
Expand All @@ -155,7 +155,7 @@ impl<C: Context> From<DefinitionDocumentError<'_, C>> for Error {
),
Some(Annotation::new(
"No definition for referenced type",
root_operation_type_definition.name_token().span().clone(),
*root_operation_type_definition.name_token().span(),
)),
Vec::new(),
),
Expand All @@ -164,7 +164,7 @@ impl<C: Context> From<DefinitionDocumentError<'_, C>> for Error {
"Schema definition does not contain a query",
Some(Annotation::new(
"Does not contain a query",
definition.root_operation_type_definitions_span().clone(),
*definition.root_operation_type_definitions_span(),
)),
Vec::new(),
)
Expand All @@ -181,28 +181,28 @@ impl<C: Context> From<DefinitionDocumentError<'_, C>> for Error {
format!("Referenced type `{}` does not exist", name.as_ref()),
Some(Annotation::new(
"No definition for referenced type",
name.span().clone(),
*name.span(),
)),
Vec::new(),
),
DefinitionDocumentError::ReferencedTypeIsNotAnInputType { name } => Error::new(
format!("Referenced type `{}` is not an input type", name.as_ref()),
Some(Annotation::new("Not an input type", name.span().clone())),
Some(Annotation::new("Not an input type", *name.span())),
Vec::new(),
),
DefinitionDocumentError::ReferencedTypeIsNotAnInterface { name } => Error::new(
format!("Referenced type `{}` is not an interface", name.as_ref()),
Some(Annotation::new("Not an interface", name.span().clone())),
Some(Annotation::new("Not an interface", *name.span())),
Vec::new(),
),
DefinitionDocumentError::ReferencedTypeIsNotAnOutputType { name } => Error::new(
format!("Referenced type `{}` is not an output type", name.as_ref()),
Some(Annotation::new("Not an output type", name.span().clone())),
Some(Annotation::new("Not an output type", *name.span())),
Vec::new(),
),
DefinitionDocumentError::ReferencedUnionMemberTypeIsNotAnObject { name } => Error::new(
format!("Referenced type `{}` is not an object", name.as_ref()),
Some(Annotation::new("Not an object type", name.span().clone())),
Some(Annotation::new("Not an object type", *name.span())),
Vec::new(),
),
DefinitionDocumentError::ImplicitRootOperationTypeNotAnObject { definition } => {
Expand All @@ -214,14 +214,14 @@ impl<C: Context> From<DefinitionDocumentError<'_, C>> for Error {
Some(Annotation::new(
"Not an object type",
// ok to unwrap because builtin scalar cannot be an implicit schema definition member
definition.name_token().unwrap().span().clone(),
*definition.name_token().unwrap().span(),
)),
Vec::new(),
)
}
DefinitionDocumentError::ExplicitRootOperationTypeNotAnObject { name } => Error::new(
format!("Referenced type `{}` is not an object", name.as_ref()),
Some(Annotation::new("Not an object type", name.span().clone())),
Some(Annotation::new("Not an object type", *name.span())),
Vec::new(),
),
DefinitionDocumentError::ReferencedDirectiveDoesNotExist { directive } => Error::new(
Expand All @@ -231,7 +231,7 @@ impl<C: Context> From<DefinitionDocumentError<'_, C>> for Error {
),
Some(Annotation::new(
"No definition for referenced directive",
directive.span().clone(),
*directive.span(),
)),
Vec::new(),
),
Expand Down
2 changes: 1 addition & 1 deletion bluejay-parser/src/ast/definition/enum_value_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl<'a, C: Context> FromTokens<'a> for EnumValueDefinition<'a, C> {
let name = tokens.expect_name()?;
if matches!(name.as_str(), "null" | "true" | "false") {
return Err(ParseError::InvalidEnumValue {
span: name.span().clone(),
span: *name.span(),
value: name.as_str().to_string(),
});
}
Expand Down
2 changes: 1 addition & 1 deletion bluejay-parser/src/ast/definition/input_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ impl<'a, C: Context + 'a> FromTokens<'a> for InputType<'a, C> {
let span = if let Some(bang_span) = &bang_span {
base_name.span().merge(bang_span)
} else {
base_name.span().clone()
*base_name.span()
};
let base = BaseInputType {
name: base_name,
Expand Down
2 changes: 1 addition & 1 deletion bluejay-parser/src/ast/definition/output_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ impl<'a, C: Context + 'a> FromTokens<'a> for OutputType<'a, C> {
let span = if let Some(bang_span) = &bang_span {
base_name.span().merge(bang_span)
} else {
base_name.span().clone()
*base_name.span()
};
let base = BaseOutputType::new(base_name);
Ok(Self::Base(base, bang_span.is_some(), span))
Expand Down
2 changes: 2 additions & 0 deletions bluejay-parser/src/ast/depth_limiter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub const DEFAULT_MAX_DEPTH: usize = 2000;

/// A depth limiter is used to limit the depth of the AST. This is useful to prevent stack overflows.
/// This intentionally does not implement `Clone` or `Copy` to prevent passing this down the call stack without bumping.
#[derive(Clone, Copy)]
pub struct DepthLimiter {
max_depth: usize,
current_depth: usize,
Expand All @@ -26,6 +27,7 @@ impl DepthLimiter {
}
}

#[inline]
pub fn bump(&self) -> Result<Self, ParseError> {
if self.current_depth >= self.max_depth {
Err(ParseError::MaxDepthExceeded)
Expand Down
2 changes: 1 addition & 1 deletion bluejay-parser/src/ast/directives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ impl<'a, const CONST: bool> FromTokens<'a> for Directives<'a, CONST> {
}
let span = match directives.as_slice() {
[] => None,
[first] => Some(first.span().clone()),
[first] => Some(*first.span()),
[first, .., last] => Some(first.span().merge(last.span())),
};
Ok(Self { directives, span })
Expand Down
6 changes: 3 additions & 3 deletions bluejay-parser/src/ast/executable/executable_document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,14 @@ mod tests {
fn test_depth_limit() {
// Depth is bumped to 1 entering the selection set (`{`)
// Depth is bumped to 2 entering the field (`foo`)
// Depth is bumped to 3 checking for args on the field (`foo`)
// Depth is only bumped further when args/directives/sub-selections are present
let document = r#"query { foo }"#;

let errors = ExecutableDocument::parse_with_options(
document,
ParseOptions {
graphql_ruby_compatibility: false,
max_depth: 2,
max_depth: 1,
max_tokens: None,
},
)
Expand All @@ -198,7 +198,7 @@ mod tests {
document,
ParseOptions {
graphql_ruby_compatibility: false,
max_depth: 3,
max_depth: 2,
max_tokens: None,
},
)
Expand Down
42 changes: 28 additions & 14 deletions bluejay-parser/src/ast/executable/field.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use crate::ast::executable::SelectionSet;
use crate::ast::{
DepthLimiter, FromTokens, IsMatch, ParseError, Tokens, TryFromTokens, VariableArguments,
VariableDirectives,
DepthLimiter, FromTokens, IsMatch, ParseError, Tokens, VariableArguments, VariableDirectives,
};
use crate::lexical_token::{Name, PunctuatorType};
use crate::{HasSpan, Span};
Expand All @@ -24,21 +23,36 @@ impl<'a> FromTokens<'a> for Field<'a> {
tokens: &mut impl Tokens<'a>,
depth_limiter: DepthLimiter,
) -> Result<Self, ParseError> {
let has_alias = tokens.peek_punctuator_matches(1, PunctuatorType::Colon);
let (alias, name) = if has_alias {
let alias = Some(tokens.expect_name()?);
tokens.expect_punctuator(PunctuatorType::Colon)?;
// Consume the first name, then check if followed by `:` (alias syntax).
// This avoids the peek(1) that requires buffering 2 tokens.
let first_name = tokens.expect_name()?;
let (alias, name) = if tokens.next_if_punctuator(PunctuatorType::Colon).is_some() {
let name = tokens.expect_name()?;
(alias, name)
(Some(first_name), name)
} else {
(None, tokens.expect_name()?)
(None, first_name)
};
let arguments = if VariableArguments::is_match(tokens) {
Some(VariableArguments::from_tokens(
tokens,
depth_limiter.bump()?,
)?)
} else {
None
};
let directives = if VariableDirectives::is_match(tokens) {
Some(VariableDirectives::from_tokens(
tokens,
depth_limiter.bump()?,
)?)
} else {
None
};
let selection_set = if SelectionSet::is_match(tokens) {
Some(SelectionSet::from_tokens(tokens, depth_limiter.bump()?)?)
} else {
None
};
let arguments =
VariableArguments::try_from_tokens(tokens, depth_limiter.bump()?).transpose()?;
let directives =
VariableDirectives::try_from_tokens(tokens, depth_limiter.bump()?).transpose()?;
let selection_set =
SelectionSet::try_from_tokens(tokens, depth_limiter.bump()?).transpose()?;
let start_span = alias.as_ref().unwrap_or(&name).span();
let directives_span = directives.as_ref().and_then(|directives| directives.span());
let end_span = if let Some(selection_set) = &selection_set {
Expand Down
21 changes: 14 additions & 7 deletions bluejay-parser/src/ast/executable/inline_fragment.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use crate::ast::executable::{SelectionSet, TypeCondition};
use crate::ast::{
DepthLimiter, FromTokens, IsMatch, ParseError, Tokens, TryFromTokens, VariableDirectives,
};
use crate::ast::{DepthLimiter, FromTokens, IsMatch, ParseError, Tokens, VariableDirectives};
use crate::lexical_token::PunctuatorType;
use crate::{HasSpan, Span};

Expand All @@ -20,10 +18,19 @@ impl<'a> FromTokens<'a> for InlineFragment<'a> {
depth_limiter: DepthLimiter,
) -> Result<Self, ParseError> {
let ellipse_span = tokens.expect_punctuator(PunctuatorType::Ellipse)?;
let type_condition =
TypeCondition::try_from_tokens(tokens, depth_limiter.bump()?).transpose()?;
let directives =
VariableDirectives::try_from_tokens(tokens, depth_limiter.bump()?).transpose()?;
let type_condition = if TypeCondition::is_match(tokens) {
Some(TypeCondition::from_tokens(tokens, depth_limiter.bump()?)?)
} else {
None
};
let directives = if VariableDirectives::is_match(tokens) {
Some(VariableDirectives::from_tokens(
tokens,
depth_limiter.bump()?,
)?)
} else {
None
};
let selection_set = SelectionSet::from_tokens(tokens, depth_limiter.bump()?)?;
let span = ellipse_span.merge(selection_set.span());
Ok(Self {
Expand Down
2 changes: 1 addition & 1 deletion bluejay-parser/src/ast/executable/variable_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ impl<'a> FromTokens<'a> for VariableType<'a> {
let is_required = bang_span.is_some();
let span = bang_span
.map(|bang_span| name.span().merge(&bang_span))
.unwrap_or_else(|| name.span().clone());
.unwrap_or_else(|| *name.span());
Ok(Self::Named {
name,
is_required,
Expand Down
Loading
Loading