-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
#### Why are we making this change? There are parts of the latest spec that `graphql-parser` doesn't support, so I want to replace it with an alternative. Other options out there that I'm aware of: - `async-graphql-parser` would work but its AST is quite annoying to work with, and last time I tested it was slower than `graphql-parser`. - `apollo-parser` continues on failure to parse, which is nice but means there are a lot of `Option`s in its AST. I don't particularly want that behaviour so that's out. - `apollo-compiler` would probably make `apollo-parser` workable, but feels like too heavy a dependency for my needs. So it looks like I might write my own. Also, it'll be fun. #### What effects does this change have? Adds the first pass of a parser for GraphQL type system documents.
- Loading branch information
Showing
13 changed files
with
2,154 additions
and
14 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
[package] | ||
name = "cynic-parser" | ||
version = "0.1.0" | ||
edition = "2021" | ||
publish = false | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[features] | ||
# TODO: Disable this at some point | ||
default = ["sdl"] | ||
sdl = ["pretty"] | ||
|
||
[dependencies] | ||
indexmap = "2" | ||
lalrpop-util = "0.20.0" | ||
logos = "0.13" | ||
pretty = { version = "0.12", optional = true } | ||
|
||
[dev-dependencies] | ||
criterion = "0.4" | ||
graphql-parser = "0.4" | ||
insta = "1.29" | ||
|
||
[build-dependencies] | ||
lalrpop = "0.20.0" | ||
|
||
|
||
[[bench]] | ||
name = "parsing-benchmark" | ||
harness = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
use criterion::{black_box, criterion_group, criterion_main, Criterion}; | ||
use cynic_parser::Ast; | ||
|
||
pub fn criterion_benchmark(c: &mut Criterion) { | ||
let input = "type MyType { field: Whatever, field: Whatever }"; | ||
c.bench_function("cynic-parser parse object", |b| { | ||
b.iter(|| { | ||
let lexer = cynic_parser::Lexer::new(input); | ||
let object = cynic_parser::ObjectDefinitionParser::new() | ||
.parse(input, &mut Ast::new(), lexer) | ||
.unwrap(); | ||
black_box(object) | ||
}) | ||
}); | ||
|
||
c.bench_function("graphql_parser parse object", |b| { | ||
b.iter(|| { | ||
let parsed = graphql_parser::parse_schema::<String>(input).unwrap(); | ||
black_box(parsed) | ||
}) | ||
}); | ||
} | ||
|
||
criterion_group!(benches, criterion_benchmark); | ||
criterion_main!(benches); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
fn main() { | ||
lalrpop::process_root().unwrap(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
use std::collections::HashMap; | ||
|
||
use ids::*; | ||
use indexmap::IndexSet; | ||
|
||
pub(crate) mod ids; | ||
mod reader; | ||
|
||
pub use reader::{AstReader, Definition, ValueReader}; | ||
|
||
#[derive(Default)] | ||
pub struct Ast { | ||
strings: IndexSet<Box<str>>, | ||
|
||
nodes: Vec<Node>, | ||
|
||
definition_nodes: Vec<NodeId>, | ||
|
||
schema_definitions: Vec<SchemaDefinition>, | ||
object_definitions: Vec<ObjectDefinition>, | ||
input_object_definitions: Vec<InputObjectDefinition>, | ||
|
||
field_definitions: Vec<FieldDefinition>, | ||
input_value_definitions: Vec<InputValueDefinition>, | ||
|
||
type_references: Vec<Type>, | ||
|
||
string_literals: Vec<StringLiteral>, | ||
|
||
values: Vec<Value>, | ||
directives: Vec<Directive>, | ||
arguments: Vec<Argument>, | ||
|
||
definition_descriptions: HashMap<NodeId, NodeId>, | ||
} | ||
|
||
// TODO: NonZeroUsize these? | ||
pub struct Node { | ||
contents: NodeContents, | ||
// span: Span | ||
} | ||
|
||
pub enum NodeContents { | ||
Ident(StringId), | ||
SchemaDefinition(SchemaDefinitionId), | ||
ObjectDefiniton(ObjectDefinitionId), | ||
FieldDefinition(FieldDefinitionId), | ||
InputObjectDefiniton(InputObjectDefinitionId), | ||
InputValueDefinition(InputValueDefinitionId), | ||
StringLiteral(StringLiteralId), | ||
} | ||
|
||
pub struct SchemaDefinition { | ||
pub roots: Vec<RootOperationTypeDefinition>, | ||
} | ||
|
||
pub struct ObjectDefinition { | ||
pub name: StringId, | ||
pub fields: Vec<NodeId>, | ||
pub directives: Vec<DirectiveId>, | ||
} | ||
|
||
pub struct FieldDefinition { | ||
pub name: StringId, | ||
pub ty: TypeId, | ||
pub arguments: Vec<NodeId>, | ||
pub description: Option<NodeId>, | ||
} | ||
|
||
pub struct InputObjectDefinition { | ||
pub name: StringId, | ||
pub fields: Vec<NodeId>, | ||
pub directives: Vec<DirectiveId>, | ||
} | ||
|
||
pub struct InputValueDefinition { | ||
pub name: StringId, | ||
pub ty: TypeId, | ||
pub description: Option<NodeId>, | ||
pub default: Option<ValueId>, | ||
} | ||
|
||
pub struct RootOperationTypeDefinition { | ||
pub operation_type: OperationType, | ||
pub named_type: StringId, | ||
} | ||
|
||
pub struct Type { | ||
pub name: StringId, | ||
pub wrappers: Vec<WrappingType>, | ||
} | ||
|
||
pub enum WrappingType { | ||
NonNull, | ||
List, | ||
} | ||
|
||
#[derive(Clone, Copy, Debug)] | ||
pub enum OperationType { | ||
Query, | ||
Mutation, | ||
Subscription, | ||
} | ||
|
||
impl std::fmt::Display for OperationType { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
OperationType::Query => write!(f, "query"), | ||
OperationType::Mutation => write!(f, "mutation"), | ||
OperationType::Subscription => write!(f, "subscription"), | ||
} | ||
} | ||
} | ||
|
||
pub enum StringLiteral { | ||
Normal(StringId), | ||
Block(StringId), | ||
} | ||
|
||
pub struct Directive { | ||
pub name: StringId, | ||
pub arguments: Vec<ArgumentId>, | ||
} | ||
|
||
pub struct Argument { | ||
pub name: StringId, | ||
pub value: ValueId, | ||
} | ||
|
||
pub enum Value { | ||
Variable(StringId), | ||
Int(i32), | ||
Float(f32), | ||
String(StringId), | ||
Boolean(bool), | ||
Null, | ||
Enum(StringId), | ||
List(Vec<ValueId>), | ||
Object(Vec<(StringId, ValueId)>), | ||
} | ||
|
||
// TODO: Don't forget the spans etc. | ||
// TODO: make this whole impl into a builder that wraps an Ast. | ||
// Then the default Reader stuff can just go on Ast - much more sensible... | ||
impl Ast { | ||
pub fn new() -> Self { | ||
Ast::default() | ||
} | ||
|
||
pub fn definitions(&mut self, ids: Vec<(Option<NodeId>, NodeId)>) { | ||
for (description, definition) in ids { | ||
if let Some(description) = description { | ||
self.definition_descriptions.insert(definition, description); | ||
} | ||
self.definition_nodes.push(definition); | ||
} | ||
} | ||
|
||
pub fn schema_definition(&mut self, definition: SchemaDefinition) -> NodeId { | ||
let definition_id = SchemaDefinitionId(self.schema_definitions.len()); | ||
self.schema_definitions.push(definition); | ||
|
||
let node_id = NodeId(self.nodes.len()); | ||
let contents = NodeContents::SchemaDefinition(definition_id); | ||
|
||
self.nodes.push(Node { contents }); | ||
|
||
node_id | ||
} | ||
|
||
pub fn object_definition(&mut self, definition: ObjectDefinition) -> NodeId { | ||
let definition_id = ObjectDefinitionId(self.object_definitions.len()); | ||
self.object_definitions.push(definition); | ||
|
||
let node_id = NodeId(self.nodes.len()); | ||
let contents = NodeContents::ObjectDefiniton(definition_id); | ||
|
||
self.nodes.push(Node { contents }); | ||
|
||
node_id | ||
} | ||
|
||
pub fn field_definition(&mut self, definition: FieldDefinition) -> NodeId { | ||
let definition_id = FieldDefinitionId(self.field_definitions.len()); | ||
self.field_definitions.push(definition); | ||
|
||
let node_id = NodeId(self.nodes.len()); | ||
let contents = NodeContents::FieldDefinition(definition_id); | ||
|
||
self.nodes.push(Node { contents }); | ||
|
||
node_id | ||
} | ||
|
||
pub fn input_object_definition(&mut self, definition: InputObjectDefinition) -> NodeId { | ||
let definition_id = InputObjectDefinitionId(self.input_object_definitions.len()); | ||
self.input_object_definitions.push(definition); | ||
|
||
let node_id = NodeId(self.nodes.len()); | ||
let contents = NodeContents::InputObjectDefiniton(definition_id); | ||
|
||
self.nodes.push(Node { contents }); | ||
|
||
node_id | ||
} | ||
|
||
pub fn input_value_definition(&mut self, definition: InputValueDefinition) -> NodeId { | ||
let definition_id = InputValueDefinitionId(self.input_value_definitions.len()); | ||
self.input_value_definitions.push(definition); | ||
|
||
let node_id = NodeId(self.nodes.len()); | ||
let contents = NodeContents::InputValueDefinition(definition_id); | ||
self.nodes.push(Node { contents }); | ||
|
||
node_id | ||
} | ||
|
||
pub fn type_reference(&mut self, ty: Type) -> TypeId { | ||
let ty_id = TypeId(self.type_references.len()); | ||
self.type_references.push(ty); | ||
ty_id | ||
} | ||
|
||
pub fn directive(&mut self, directive: Directive) -> DirectiveId { | ||
let id = DirectiveId(self.directives.len()); | ||
self.directives.push(directive); | ||
id | ||
} | ||
|
||
pub fn argument(&mut self, argument: Argument) -> ArgumentId { | ||
let id = ArgumentId(self.arguments.len()); | ||
self.arguments.push(argument); | ||
id | ||
} | ||
|
||
pub fn value(&mut self, value: Value) -> ValueId { | ||
let id = ValueId(self.values.len()); | ||
self.values.push(value); | ||
id | ||
} | ||
|
||
pub fn string_literal(&mut self, literal: StringLiteral) -> NodeId { | ||
let literal_id = StringLiteralId(self.string_literals.len()); | ||
self.string_literals.push(literal); | ||
|
||
let node_id = NodeId(self.nodes.len()); | ||
let contents = NodeContents::StringLiteral(literal_id); | ||
self.nodes.push(Node { contents }); | ||
|
||
node_id | ||
} | ||
|
||
pub fn ident(&mut self, ident: &str) -> StringId { | ||
self.intern_string(ident) | ||
} | ||
|
||
// TOOD: should this be pub? not sure... | ||
pub fn intern_string(&mut self, string: &str) -> StringId { | ||
let (id, _) = self.strings.insert_full(string.into()); | ||
StringId(id) | ||
} | ||
} |
Oops, something went wrong.