Skip to content
Merged
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
122 changes: 73 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# GraphQL Dotnet Parser

[![AppVeyor](https://img.shields.io/appveyor/ci/graphql-dotnet-ci/parser.svg)](https://ci.appveyor.com/project/graphql-dotnet-ci/parser)
[![Coverage Status](https://coveralls.io/repos/github/graphql-dotnet/parser/badge.svg?branch=master)](https://coveralls.io/github/graphql-dotnet/parser?branch=master)
[![NuGet](https://img.shields.io/nuget/v/GraphQL-Parser.svg)](https://www.nuget.org/packages/GraphQL-Parser)
Expand All @@ -21,13 +22,19 @@ Generates token based on input text. Lexer takes advantage of `ReadOnlyMemory<ch
does not allocate memory on the managed heap at all.

### Usage

Directly:

```c#
var token = Lexer.Lex("\"str\"");
```

or
Or via extension method:

```c#
var token = "\"str\"".Lex();
```

Lex method always returns the first token it finds. In this case case the result would look like following.
![lexer example](assets/lexer-example.png)

Expand All @@ -37,67 +44,84 @@ Parses provided GraphQL expression into AST (abstract syntax tree). Parser also
`ReadOnlyMemory<char>` but still allocates memory for AST.

### Usage

Directly:

```c#
var ast = Parser.Parse(@"
var ast1 = Parser.Parse(@"
{
field
}", ignoreComments: true);
}");

or
var ast2 = Parser.Parse(@"
{
field
}", new ParserOptions { Ignore = IgnoreOptions.IgnoreComments });
```

Or via extension method:

```c#
var ast = @"
{
field
}".Parse();

var ast = @"
{
field
}".Parse(ignoreComments: true);
}".Parse(new ParserOptions { Ignore = IgnoreOptions.IgnoreCommentsAndLocations });
```

By default `ignoreComments` is `true` to improve performance.
By default `ParserOptions.Ignore` is `IgnoreOptions.IgnoreComments` to improve performance.
If you don't need information about tokens locations in the source document, then use `IgnoreOptions.IgnoreCommentsAndLocations`.
This will maximize the saving of memory allocated in the managed heap for AST.

Json representation of the resulting AST would be:
### Example of json representation of the resulting AST

```json
{
"Definitions": [{
"Directives": [],
"Kind": 2,
"Name": null,
"Operation": 0,
"SelectionSet": {
"Kind": 5,
"Selections": [{
"Alias": null,
"Arguments": [],
"Directives": [],
"Kind": 6,
"Name": {
"Kind": 0,
"Value": "field",
"Location": {
"End": 50,
"Start": 31
}
},
"SelectionSet": null,
"Location": {
"End": 50,
"Start": 31
}
}],
"Location": {
"End": 50,
"Start": 13
}
},
"VariableDefinitions": null,
"Location": {
"End": 50,
"Start": 13
}
}],
"Kind": 1,
"Location": {
"End": 50,
"Start": 13
}
"Definitions": [{
"Directives": [],
"Kind": 2,
"Name": null,
"Operation": 0,
"SelectionSet": {
"Kind": 5,
"Selections": [{
"Alias": null,
"Arguments": [],
"Directives": [],
"Kind": 6,
"Name": {
"Kind": 0,
"Value": "field",
"Location": {
"End": 50,
"Start": 31
}
},
"SelectionSet": null,
"Location": {
"End": 50,
"Start": 31
}
}],
"Location": {
"End": 50,
"Start": 13
}
},
"VariableDefinitions": null,
"Location": {
"End": 50,
"Start": 13
}
}],
"Kind": 1,
"Location": {
"End": 50,
"Start": 13
}
}
```
Binary file modified assets/lexer-example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 15 additions & 4 deletions src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ namespace GraphQLParser.AST
public abstract class ASTNode
{
protected ASTNode() { }
public GraphQLParser.AST.GraphQLComment? Comment { get; set; }
public abstract GraphQLParser.AST.ASTNodeKind Kind { get; }
public GraphQLParser.AST.GraphQLLocation Location { get; set; }
public virtual GraphQLParser.AST.GraphQLComment? Comment { get; set; }
public virtual GraphQLParser.AST.GraphQLLocation Location { get; set; }
}
public enum ASTNodeKind
{
Expand Down Expand Up @@ -60,6 +60,7 @@ namespace GraphQLParser.AST
public GraphQLComment() { }
public override GraphQLParser.AST.ASTNodeKind Kind { get; }
public GraphQLParser.ROM Text { get; set; }
public override GraphQLParser.AST.GraphQLLocation Location { get; set; }
}
public class GraphQLDirective : GraphQLParser.AST.ASTNode, GraphQLParser.AST.INamedNode
{
Expand Down Expand Up @@ -374,6 +375,12 @@ namespace GraphQLParser
public virtual GraphQLParser.AST.GraphQLVariable EndVisitVariable(GraphQLParser.AST.GraphQLVariable variable) { }
public virtual void Visit(GraphQLParser.AST.GraphQLDocument ast) { }
}
public enum IgnoreOptions
{
IgnoreComments = 0,
IgnoreCommentsAndLocations = 1,
None = 2,
}
public static class Lexer
{
public static GraphQLParser.Token Lex(GraphQLParser.ROM source, int start = 0) { }
Expand All @@ -386,12 +393,16 @@ namespace GraphQLParser
}
public static class Parser
{
public static GraphQLParser.AST.GraphQLDocument Parse(GraphQLParser.ROM source, bool ignoreComments = true) { }
public static GraphQLParser.AST.GraphQLDocument Parse(GraphQLParser.ROM source, GraphQLParser.ParserOptions options = default) { }
}
public static class ParserExtensions
{
public static GraphQLParser.Token Lex(this string source, int start = 0) { }
public static GraphQLParser.AST.GraphQLDocument Parse(this string source, bool ignoreComments = true) { }
public static GraphQLParser.AST.GraphQLDocument Parse(this string source, GraphQLParser.ParserOptions options = default) { }
}
public struct ParserOptions
{
public GraphQLParser.IgnoreOptions Ignore { get; set; }
}
public readonly struct ROM : System.IEquatable<GraphQLParser.ROM>
{
Expand Down
4 changes: 4 additions & 0 deletions src/GraphQLParser.Benchmarks/Benchmarks/BenchmarkBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public abstract class BenchmarkBase : IBenchmark
private string _kitchen = null!;
private string _introspection = null!;
private string _params = null!;
private string _variables = null!;
private string _github = null!;

[GlobalSetup]
Expand All @@ -20,6 +21,7 @@ public virtual void GlobalSetup()
_kitchen = "kitchenSink".ReadGraphQLFile();
_introspection = "introspectionQuery".ReadGraphQLFile();
_params = "params".ReadGraphQLFile();
_variables = "variables".ReadGraphQLFile();
_github = "github".ReadGraphQLFile();
}

Expand All @@ -32,6 +34,7 @@ public string GetQueryByName(string name)
"kitchen" => _kitchen,
"introspection" => _introspection,
"params" => _params,
"variables" => _variables,
"github" => _github,
_ => throw new System.Exception(name)
};
Expand All @@ -44,6 +47,7 @@ public IEnumerable<string> Names()
yield return "kitchen";
yield return "introspection";
yield return "params";
yield return "variables";
yield return "github";
}

Expand Down
84 changes: 80 additions & 4 deletions src/GraphQLParser.Benchmarks/Benchmarks/ParserBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,95 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;

namespace GraphQLParser.Benchmarks
{
[Config(typeof(Config))]
[MemoryDiagnoser]
//[RPlotExporter, CsvMeasurementsExporter]
public class ParserBenchmark : BenchmarkBase
{
private class Config : ManualConfig
{
public Config()
{
SummaryStyle = new SummaryStyle(CultureInfo.InvariantCulture, printUnitsInHeader: true, sizeUnit: null, timeUnit: null, printUnitsInContent: false, printZeroValuesInContent: false, maxParameterColumnWidth: 40);
Orderer = new ParserOrderer();
}

private class ParserOrderer : IOrderer
{
private static int GetOrder(object o) => o switch
{
IgnoreOptions.None => 1,
IgnoreOptions.IgnoreComments => 2,
IgnoreOptions.IgnoreCommentsAndLocations => 3,
_ => 4
};

public IEnumerable<BenchmarkCase> GetExecutionOrder(ImmutableArray<BenchmarkCase> benchmarksCase) => benchmarksCase;

public IEnumerable<BenchmarkCase> GetSummaryOrder(ImmutableArray<BenchmarkCase> benchmarksCase, Summary summary) =>
from benchmark in benchmarksCase
orderby GetOrder(benchmark.Parameters["options"])
orderby benchmark.Parameters["name"]
select benchmark;

public string GetHighlightGroupKey(BenchmarkCase benchmarkCase) => null!;

public string GetLogicalGroupKey(ImmutableArray<BenchmarkCase> allBenchmarksCases, BenchmarkCase benchmarkCase) => (string)benchmarkCase.Parameters["name"];

public IEnumerable<IGrouping<string, BenchmarkCase>> GetLogicalGroupOrder(IEnumerable<IGrouping<string, BenchmarkCase>> logicalGroups) => logicalGroups.OrderBy(it => it.Key);

public bool SeparateLogicalGroups => true;
}
}

[Benchmark]
[ArgumentsSource(nameof(Names))]
public void Parse(string name)
[ArgumentsSource(nameof(NamesAndOptions))]
public void Parse(string name, IgnoreOptions options)
{
var source = GetQueryByName(name);
source.Parse().Dispose();
source.Parse(new ParserOptions { Ignore = options }).Dispose();
}

public IEnumerable<object[]> NamesAndOptions()
{
yield return new object[] { "hero", IgnoreOptions.None };
yield return new object[] { "hero", IgnoreOptions.IgnoreComments };
yield return new object[] { "hero", IgnoreOptions.IgnoreCommentsAndLocations };

yield return new object[] { "escapes", IgnoreOptions.None };
yield return new object[] { "escapes", IgnoreOptions.IgnoreComments };
yield return new object[] { "escapes", IgnoreOptions.IgnoreCommentsAndLocations };

yield return new object[] { "kitchen", IgnoreOptions.None };
yield return new object[] { "kitchen", IgnoreOptions.IgnoreComments };
yield return new object[] { "kitchen", IgnoreOptions.IgnoreCommentsAndLocations };

yield return new object[] { "introspection", IgnoreOptions.None };
yield return new object[] { "introspection", IgnoreOptions.IgnoreComments };
yield return new object[] { "introspection", IgnoreOptions.IgnoreCommentsAndLocations };

yield return new object[] { "params", IgnoreOptions.None };
yield return new object[] { "params", IgnoreOptions.IgnoreComments };
yield return new object[] { "params", IgnoreOptions.IgnoreCommentsAndLocations };

yield return new object[] { "variables", IgnoreOptions.None };
yield return new object[] { "variables", IgnoreOptions.IgnoreComments };
yield return new object[] { "variables", IgnoreOptions.IgnoreCommentsAndLocations };

yield return new object[] { "github", IgnoreOptions.None };
yield return new object[] { "github", IgnoreOptions.IgnoreComments };
yield return new object[] { "github", IgnoreOptions.IgnoreCommentsAndLocations };
}

public override void Run() => Parse("params");
public override void Run() => Parse("params", IgnoreOptions.None);
}
}
32 changes: 32 additions & 0 deletions src/GraphQLParser.Benchmarks/Files/variables.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
query var($condition: Boolean! = true, $count: Int! = 1000, $name: String! = "Tom", $address: String! = null) {
alias1: field1(c: $condition, cnt: $count, name: $name, addr: $address, info: null) {
field2(c: $condition, cnt: $count, name: $name, addr: $address, info: null) {
field3(c: $condition, cnt: $count, name: $name, addr: $address, info: null) {
field4(c: $condition, cnt: $count, name: $name, addr: $address, info: null) {
field5(c: $condition, cnt: $count, name: $name, addr: $address, info: null)
inner: value
}
}
}
}
alias2: field1(c: $condition, cnt: $count, name: $name, addr: $address, info: null) {
field2(c: $condition, cnt: $count, name: $name, addr: $address, info: null) {
field3(c: $condition, cnt: $count, name: $name, addr: $address, info: null) {
field4(c: $condition, cnt: $count, name: $name, addr: $address, info: null) {
field5(c: $condition, cnt: $count, name: $name, addr: $address, info: null)
inner: value
}
}
}
}
alias3: field1(c: $condition, cnt: $count, name: $name, addr: $address, info: null) {
field2(c: $condition, cnt: $count, name: $name, addr: $address, info: null) {
field3(c: $condition, cnt: $count, name: $name, addr: $address, info: null) {
field4(c: $condition, cnt: $count, name: $name, addr: $address, info: null) {
field5(c: $condition, cnt: $count, name: $name, addr: $address, info: null)
inner: value
}
}
}
}
}
5 changes: 4 additions & 1 deletion src/GraphQLParser.Benchmarks/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{
"profiles": {
"Benchmark": {
"commandName": "Project"
},
"Profiler": {
"commandName": "Project",
"commandLineArgs": "test"
}
}
}
}
Loading