diff --git a/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt b/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt index ce24bfcd..6020c9f5 100644 --- a/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt +++ b/src/GraphQLParser.ApiTests/GraphQL-Parser.approved.txt @@ -82,6 +82,7 @@ namespace GraphQLParser.AST public GraphQLDocument() { } public System.Collections.Generic.List? Definitions { get; set; } public override GraphQLParser.AST.ASTNodeKind Kind { get; } + public System.Collections.Generic.List? UnattachedComments { get; set; } } public class GraphQLEnumTypeDefinition : GraphQLParser.AST.GraphQLTypeDefinition, GraphQLParser.AST.IHasDirectivesNode { diff --git a/src/GraphQLParser.Tests/ParserTests.cs b/src/GraphQLParser.Tests/ParserTests.cs index de624711..b28a682f 100644 --- a/src/GraphQLParser.Tests/ParserTests.cs +++ b/src/GraphQLParser.Tests/ParserTests.cs @@ -12,6 +12,50 @@ public class ParserTests { private static readonly string _nl = Environment.NewLine; + [Fact] + public void Extra_Comments_Should_Read_Correclty() + { + const string query = @" +query _ { + person { + name + #comment1 + } + #comment2 + test { + alt + } + #comment3 +} +#comment4 +"; + + var parser = new Parser(new Lexer()); + var document = parser.Parse(new Source(query)); + document.Definitions.Count().ShouldBe(1); + // query + var def = document.Definitions.First() as GraphQLOperationDefinition; + def.SelectionSet.Selections.Count().ShouldBe(2); + // person + var field = def.SelectionSet.Selections.First() as GraphQLFieldSelection; + field.SelectionSet.Selections.Count().ShouldBe(1); + // name + var subField = field.SelectionSet.Selections.First() as GraphQLFieldSelection; + subField.Comment.ShouldBeNull(); + // test + field = def.SelectionSet.Selections.Last() as GraphQLFieldSelection; + field.SelectionSet.Selections.Count().ShouldBe(1); + field.Comment.Text.ShouldBe("comment2"); + // alt + subField = field.SelectionSet.Selections.First() as GraphQLFieldSelection; + subField.Comment.ShouldBeNull(); + // extra document comments + document.UnattachedComments.Count().ShouldBe(3); + document.UnattachedComments[0].Text.ShouldBe("comment1"); + document.UnattachedComments[1].Text.ShouldBe("comment3"); + document.UnattachedComments[2].Text.ShouldBe("comment4"); + } + [Fact] public void Comments_on_FragmentSpread_Should_Read_Correclty() { diff --git a/src/GraphQLParser/AST/GraphQLDocument.cs b/src/GraphQLParser/AST/GraphQLDocument.cs index 38b67234..b05b0d90 100644 --- a/src/GraphQLParser/AST/GraphQLDocument.cs +++ b/src/GraphQLParser/AST/GraphQLDocument.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace GraphQLParser.AST { @@ -7,5 +7,6 @@ public class GraphQLDocument : ASTNode public List? Definitions { get; set; } public override ASTNodeKind Kind => ASTNodeKind.Document; + public List? UnattachedComments { get; set; } } -} \ No newline at end of file +} diff --git a/src/GraphQLParser/Parser.cs b/src/GraphQLParser/Parser.cs index f56cfdac..295942be 100644 --- a/src/GraphQLParser/Parser.cs +++ b/src/GraphQLParser/Parser.cs @@ -13,7 +13,7 @@ public Parser(ILexer lexer) public GraphQLDocument Parse(ISource source) { - using var context = new ParserContext(source, _lexer); + var context = new ParserContext(source, _lexer); return context.Parse(); } } diff --git a/src/GraphQLParser/ParserContext.cs b/src/GraphQLParser/ParserContext.cs index 90d170b5..cdf8e220 100644 --- a/src/GraphQLParser/ParserContext.cs +++ b/src/GraphQLParser/ParserContext.cs @@ -3,337 +3,339 @@ using GraphQLParser.AST; using GraphQLParser.Exceptions; -namespace GraphQLParser -{ - // WARNING: mutable struct, pass it by reference to those methods that will change it - internal struct ParserContext : IDisposable +namespace GraphQLParser +{ + // WARNING: mutable struct, pass it by reference to those methods that will change it + internal struct ParserContext { - private delegate TResult ParseCallback(ref ParserContext context); - - private readonly ILexer _lexer; - private readonly ISource _source; - private Stack? _comments; - private Token _currentToken; - private Token _prevToken; - - public ParserContext(ISource source, ILexer lexer) - { - _comments = null; - _source = source; - _lexer = lexer; - - _currentToken = _lexer.Lex(source); + private delegate TResult ParseCallback(ref ParserContext context); + + private readonly ILexer _lexer; + private readonly ISource _source; + private GraphQLComment? _comment; + private List? _comments; + private Token _currentToken; + private Token _prevToken; + + public ParserContext(ISource source, ILexer lexer) + { + _comment = null; + _comments = null; + _source = source; + _lexer = lexer; + + _currentToken = _lexer.Lex(source); _prevToken = new Token ( TokenKind.UNKNOWN, null, 0, 0 - ); - } - - public void Dispose() - { - if (_comments?.Count > 0) - throw new ApplicationException($"ParserContext has {_comments.Count} not applied comments."); - } - - public GraphQLComment? GetComment() => _comments?.Count > 0 ? _comments.Pop() : null; - - public GraphQLDocument Parse() => ParseDocument(); - - private void Advance() - { - // We should not advance further if we have already reached the EOF. + ); + } + + public GraphQLComment? GetComment() + { + var ret = _comment; + _comment = null; + return ret; + } + + public GraphQLDocument Parse() => ParseDocument(); + + private void Advance() + { + // We should not advance further if we have already reached the EOF. if (_currentToken.Kind != TokenKind.EOF) { _prevToken = _currentToken; _currentToken = _lexer.Lex(_source, _currentToken.End); - } - } - - private GraphQLType AdvanceThroughColonAndParseType() - { - Expect(TokenKind.COLON); - return ParseType(); - } - - private List? Any(TokenKind open, ParseCallback next, TokenKind close) - where T : ASTNode - { - Expect(open); - - ParseComment(); - - List? nodes = null; - while (!Skip(close)) - (nodes ??= new List()).Add(next(ref this)); - - return nodes; - } - - private GraphQLDocument CreateDocument(int start, List definitions) - { - return new GraphQLDocument - { - Location = new GraphQLLocation - ( + } + } + + private GraphQLType AdvanceThroughColonAndParseType() + { + Expect(TokenKind.COLON); + return ParseType(); + } + + private List? Any(TokenKind open, ParseCallback next, TokenKind close) + where T : ASTNode + { + Expect(open); + + ParseComment(); + + List? nodes = null; + while (!Skip(close)) + (nodes ??= new List()).Add(next(ref this)); + + return nodes; + } + + private GraphQLDocument CreateDocument(int start, List definitions, List? comments) + { + return new GraphQLDocument + { + Location = new GraphQLLocation + ( start, // Formally, to denote the end of the document, it is better to use _prevToken.End, // since _prevToken represents some real meaningful token; _currentToken here is always EOF. // EOF is a technical token with length = 0, _prevToken.End and _currentToken.End have the same value here. - _prevToken.End // equals to _currentToken.End (EOF) - ), - Definitions = definitions - }; - } - - private GraphQLFieldSelection CreateFieldSelection(int start, GraphQLName name, GraphQLName? alias, GraphQLComment? comment) - { - return new GraphQLFieldSelection - { - Comment = comment, - Alias = alias, - Name = name, - Arguments = ParseArguments(), - Directives = ParseDirectives(), - SelectionSet = Peek(TokenKind.BRACE_L) ? ParseSelectionSet() : null, - Location = GetLocation(start) - }; - } - - private ASTNode CreateGraphQLFragmentSpread(int start, GraphQLComment? comment) - { - return new GraphQLFragmentSpread - { - Comment = comment, - Name = ParseFragmentName(), - Directives = ParseDirectives(), - Location = GetLocation(start) - }; - } - - private ASTNode CreateInlineFragment(int start, GraphQLComment? comment) - { - return new GraphQLInlineFragment - { - Comment = comment, - TypeCondition = GetTypeCondition(), - Directives = ParseDirectives(), - SelectionSet = ParseSelectionSet(), - Location = GetLocation(start) - }; - } - - private ASTNode CreateOperationDefinition(int start, OperationType operation, GraphQLName? name) - { - var comment = GetComment(); - return new GraphQLOperationDefinition - { - Comment = comment, - Operation = operation, - Name = name, - VariableDefinitions = ParseVariableDefinitions(), - Directives = ParseDirectives(), - SelectionSet = ParseSelectionSet(), - Location = GetLocation(start) - }; - } - - private ASTNode CreateOperationDefinition(int start) - { - var comment = GetComment(); - return new GraphQLOperationDefinition - { - Comment = comment, - Operation = OperationType.Query, - SelectionSet = ParseSelectionSet(), - Location = GetLocation(start) - }; - } - - private void Expect(TokenKind kind) - { - if (_currentToken.Kind == kind) - { - Advance(); - } - else - { - throw new GraphQLSyntaxErrorException( - $"Expected {Token.GetTokenKindDescription(kind)}, found {_currentToken}", - _source, - _currentToken.Start); - } - } - - private GraphQLValue ExpectColonAndParseValueLiteral(bool isConstant) - { - Expect(TokenKind.COLON); - return ParseValueLiteral(isConstant); - } - - private void ExpectKeyword(string keyword) - { - var token = _currentToken; - if (token.Kind == TokenKind.NAME && keyword.Equals(token.Value)) - { - Advance(); - return; - } - - throw new GraphQLSyntaxErrorException( - $"Expected \"{keyword}\", found Name \"{token.Value}\"", _source, _currentToken.Start); - } - - private GraphQLNamedType ExpectOnKeywordAndParseNamedType() - { - ExpectKeyword("on"); - return ParseNamedType(); - } - - private GraphQLValue? GetDefaultConstantValue() - { - GraphQLValue? defaultValue = null; - if (Skip(TokenKind.EQUALS)) - { - defaultValue = ParseConstantValue(); - } - - return defaultValue; - } - - private GraphQLLocation GetLocation(int start) - { - return new GraphQLLocation - ( - start, - _prevToken.End - ); - } - - private GraphQLName? GetName() => Peek(TokenKind.NAME) ? ParseName() : null; - - private GraphQLNamedType? GetTypeCondition() - { - GraphQLNamedType? typeCondition = null; - if (_currentToken.Value != null && _currentToken.Value.Equals("on")) - { - Advance(); - typeCondition = ParseNamedType(); - } - - return typeCondition; - } - - private List Many(TokenKind open, ParseCallback next, TokenKind close) - { - Expect(open); - - ParseComment(); - - var nodes = new List { next(ref this) }; - while (!Skip(close)) - nodes.Add(next(ref this)); - - return nodes; - } - - private GraphQLArgument ParseArgument() - { - var comment = GetComment(); - int start = _currentToken.Start; - - return new GraphQLArgument - { - Comment = comment, - Name = ParseName(), - Value = ExpectColonAndParseValueLiteral(false), - Location = GetLocation(start) - }; - } - - private List? ParseArgumentDefs() - { + _prevToken.End // equals to _currentToken.End (EOF) + ), + Definitions = definitions, + UnattachedComments = comments, + }; + } + + private GraphQLFieldSelection CreateFieldSelection(int start, GraphQLName name, GraphQLName? alias, GraphQLComment? comment) + { + return new GraphQLFieldSelection + { + Comment = comment, + Alias = alias, + Name = name, + Arguments = ParseArguments(), + Directives = ParseDirectives(), + SelectionSet = Peek(TokenKind.BRACE_L) ? ParseSelectionSet() : null, + Location = GetLocation(start) + }; + } + + private ASTNode CreateGraphQLFragmentSpread(int start, GraphQLComment? comment) + { + return new GraphQLFragmentSpread + { + Comment = comment, + Name = ParseFragmentName(), + Directives = ParseDirectives(), + Location = GetLocation(start) + }; + } + + private ASTNode CreateInlineFragment(int start, GraphQLComment? comment) + { + return new GraphQLInlineFragment + { + Comment = comment, + TypeCondition = GetTypeCondition(), + Directives = ParseDirectives(), + SelectionSet = ParseSelectionSet(), + Location = GetLocation(start) + }; + } + + private ASTNode CreateOperationDefinition(int start, OperationType operation, GraphQLName? name) + { + var comment = GetComment(); + return new GraphQLOperationDefinition + { + Comment = comment, + Operation = operation, + Name = name, + VariableDefinitions = ParseVariableDefinitions(), + Directives = ParseDirectives(), + SelectionSet = ParseSelectionSet(), + Location = GetLocation(start) + }; + } + + private ASTNode CreateOperationDefinition(int start) + { + var comment = GetComment(); + return new GraphQLOperationDefinition + { + Comment = comment, + Operation = OperationType.Query, + SelectionSet = ParseSelectionSet(), + Location = GetLocation(start) + }; + } + + private void Expect(TokenKind kind) + { + if (_currentToken.Kind == kind) + { + Advance(); + } + else + { + throw new GraphQLSyntaxErrorException( + $"Expected {Token.GetTokenKindDescription(kind)}, found {_currentToken}", + _source, + _currentToken.Start); + } + } + + private GraphQLValue ExpectColonAndParseValueLiteral(bool isConstant) + { + Expect(TokenKind.COLON); + return ParseValueLiteral(isConstant); + } + + private void ExpectKeyword(string keyword) + { + var token = _currentToken; + if (token.Kind == TokenKind.NAME && keyword.Equals(token.Value)) + { + Advance(); + return; + } + + throw new GraphQLSyntaxErrorException( + $"Expected \"{keyword}\", found Name \"{token.Value}\"", _source, _currentToken.Start); + } + + private GraphQLNamedType ExpectOnKeywordAndParseNamedType() + { + ExpectKeyword("on"); + return ParseNamedType(); + } + + private GraphQLValue? GetDefaultConstantValue() + { + GraphQLValue? defaultValue = null; + if (Skip(TokenKind.EQUALS)) + { + defaultValue = ParseConstantValue(); + } + + return defaultValue; + } + + private GraphQLLocation GetLocation(int start) + { + return new GraphQLLocation + ( + start, + _prevToken.End + ); + } + + private GraphQLName? GetName() => Peek(TokenKind.NAME) ? ParseName() : null; + + private GraphQLNamedType? GetTypeCondition() + { + GraphQLNamedType? typeCondition = null; + if (_currentToken.Value != null && _currentToken.Value.Equals("on")) + { + Advance(); + typeCondition = ParseNamedType(); + } + + return typeCondition; + } + + private List Many(TokenKind open, ParseCallback next, TokenKind close) + { + Expect(open); + + ParseComment(); + + var nodes = new List { next(ref this) }; + while (!Skip(close)) + nodes.Add(next(ref this)); + + return nodes; + } + + private GraphQLArgument ParseArgument() + { + var comment = GetComment(); + int start = _currentToken.Start; + + return new GraphQLArgument + { + Comment = comment, + Name = ParseName(), + Value = ExpectColonAndParseValueLiteral(false), + Location = GetLocation(start) + }; + } + + private List? ParseArgumentDefs() + { return Peek(TokenKind.PAREN_L) ? Many(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseInputValueDef(), TokenKind.PAREN_R) - : null; - } - - private List? ParseArguments() - { - return Peek(TokenKind.PAREN_L) ? - Many(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseArgument(), TokenKind.PAREN_R) : - null; - } - - private GraphQLValue ParseBooleanValue(Token token) - { - Advance(); - return new GraphQLScalarValue(ASTNodeKind.BooleanValue) - { - Value = token.Value, - Location = GetLocation(token.Start) - }; - } - - private GraphQLValue ParseConstantValue() => ParseValueLiteral(true); - - private ASTNode ParseDefinition() - { - ParseComment(); - - if (Peek(TokenKind.BRACE_L)) - { - return ParseOperationDefinition(); - } - - if (Peek(TokenKind.NAME)) - { - ASTNode? definition; - if ((definition = ParseNamedDefinition()) != null) - return definition; - } - - throw new GraphQLSyntaxErrorException( - $"Unexpected {_currentToken}", _source, _currentToken.Start); - } - - private List ParseDefinitionsIfNotEOF() - { - var result = new List(); - - if (_currentToken.Kind != TokenKind.EOF) - { - do - { - result.Add(ParseDefinition()); - } - while (!Skip(TokenKind.EOF)); - } - - return result; - } - - private GraphQLComment? ParseComment() - { - if (!Peek(TokenKind.COMMENT)) - { - return null; - } - - var text = new List(); - int start = _currentToken.Start; - int end; - - do - { - text.Add(_currentToken.Value); - end = _currentToken.End; - Advance(); + : null; + } + + private List? ParseArguments() + { + return Peek(TokenKind.PAREN_L) ? + Many(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseArgument(), TokenKind.PAREN_R) : + null; + } + + private GraphQLValue ParseBooleanValue(Token token) + { + Advance(); + return new GraphQLScalarValue(ASTNodeKind.BooleanValue) + { + Value = token.Value, + Location = GetLocation(token.Start) + }; + } + + private GraphQLValue ParseConstantValue() => ParseValueLiteral(true); + + private ASTNode ParseDefinition() + { + ParseComment(); + + if (Peek(TokenKind.BRACE_L)) + { + return ParseOperationDefinition(); + } + + if (Peek(TokenKind.NAME)) + { + ASTNode? definition; + if ((definition = ParseNamedDefinition()) != null) + return definition; } - while (_currentToken.Kind == TokenKind.COMMENT); - + + throw new GraphQLSyntaxErrorException( + $"Unexpected {_currentToken}", _source, _currentToken.Start); + } + + private List ParseDefinitionsIfNotEOF() + { + var result = new List(); + + if (_currentToken.Kind != TokenKind.EOF) + { + do + { + result.Add(ParseDefinition()); + } + while (!Skip(TokenKind.EOF)); + } + + return result; + } + + private GraphQLComment? ParseComment() + { + if (!Peek(TokenKind.COMMENT)) + { + return null; + } + + var text = new List(); + int start = _currentToken.Start; + int end; + + do + { + text.Add(_currentToken.Value); + end = _currentToken.End; + Advance(); + } + while (_currentToken.Kind == TokenKind.COMMENT); + var comment = new GraphQLComment(string.Join(Environment.NewLine, text)) { Location = new GraphQLLocation @@ -341,26 +343,30 @@ private List ParseDefinitionsIfNotEOF() start, end ) - }; - - if (_comments == null) - _comments = new Stack(); - - _comments.Push(comment); - - return comment; - } - - private GraphQLDirective ParseDirective() - { - int start = _currentToken.Start; - Expect(TokenKind.AT); - return new GraphQLDirective - { - Name = ParseName(), - Arguments = ParseArguments(), - Location = GetLocation(start) - }; + }; + + if (_comment != null) + { + if (_comments == null) + _comments = new List(); + _comments.Add(_comment); + } + + _comment = comment; + + return comment; + } + + private GraphQLDirective ParseDirective() + { + int start = _currentToken.Start; + Expect(TokenKind.AT); + return new GraphQLDirective + { + Name = ParseName(), + Arguments = ParseArguments(), + Location = GetLocation(start) + }; } /// @@ -368,30 +374,30 @@ private GraphQLDirective ParseDirective() /// DirectiveDefinition: /// Description(opt) directive @ Name ArgumentsDefinition(opt) repeatable(opt) on DirectiveLocations /// - /// - private GraphQLDirectiveDefinition ParseDirectiveDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("directive"); - Expect(TokenKind.AT); - - var name = ParseName(); - var args = ParseArgumentDefs(); - bool repeatable = ParseRepeatable(); - - ExpectKeyword("on"); - var locations = ParseDirectiveLocations(); - - return new GraphQLDirectiveDefinition - { - Comment = comment, - Name = name, - Repeatable = repeatable, - Arguments = args, - Locations = locations, - Location = GetLocation(start) - }; + /// + private GraphQLDirectiveDefinition ParseDirectiveDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("directive"); + Expect(TokenKind.AT); + + var name = ParseName(); + var args = ParseArgumentDefs(); + bool repeatable = ParseRepeatable(); + + ExpectKeyword("on"); + var locations = ParseDirectiveLocations(); + + return new GraphQLDirectiveDefinition + { + Comment = comment, + Name = name, + Repeatable = repeatable, + Arguments = args, + Locations = locations, + Location = GetLocation(start) + }; } private bool ParseRepeatable() @@ -411,577 +417,583 @@ private bool ParseRepeatable() } return false; - } - - private List ParseDirectiveLocations() - { + } + + private List ParseDirectiveLocations() + { var locations = new List(); // Directive locations may be defined with an optional leading | character - // to aid formatting when representing a longer list of possible locations - Skip(TokenKind.PIPE); - - do - { - locations.Add(ParseName()); - } - while (Skip(TokenKind.PIPE)); - - return locations; - } - - private List? ParseDirectives() - { - List? directives = null; - while (Peek(TokenKind.AT)) - (directives ??= new List()).Add(ParseDirective()); - - return directives; - } - - private GraphQLDocument ParseDocument() - { - int start = _currentToken.Start; - var definitions = ParseDefinitionsIfNotEOF(); - - return CreateDocument(start, definitions); - } - - private GraphQLEnumTypeDefinition ParseEnumTypeDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("enum"); - - return new GraphQLEnumTypeDefinition - { - Comment = comment, - Name = ParseName(), - Directives = ParseDirectives(), - Values = Many(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseEnumValueDefinition(), TokenKind.BRACE_R), - Location = GetLocation(start) - }; - } - - private GraphQLValue ParseEnumValue(Token token) - { - Advance(); - return new GraphQLScalarValue(ASTNodeKind.EnumValue) - { - Value = token.Value, - Location = GetLocation(token.Start) - }; - } - - private GraphQLEnumValueDefinition ParseEnumValueDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - - return new GraphQLEnumValueDefinition - { - Comment = comment, - Name = ParseName(), - Directives = ParseDirectives(), - Location = GetLocation(start) - }; - } - - private GraphQLFieldDefinition ParseFieldDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - var name = ParseName(); - var args = ParseArgumentDefs(); - Expect(TokenKind.COLON); - - return new GraphQLFieldDefinition - { - Comment = comment, - Name = name, - Arguments = args, - Type = ParseType(), - Directives = ParseDirectives(), - Location = GetLocation(start) - }; - } - - private GraphQLFieldSelection ParseFieldSelection() - { - var comment = GetComment(); - int start = _currentToken.Start; - var nameOrAlias = ParseName(); - GraphQLName name; - GraphQLName? alias; - - if (Skip(TokenKind.COLON)) - { - name = ParseName(); - alias = nameOrAlias; - } - else - { - alias = null; - name = nameOrAlias; - } - - return CreateFieldSelection(start, name, alias, comment); - } - - private GraphQLValue ParseFloat(/*bool isConstant*/) - { - var token = _currentToken; - Advance(); - return new GraphQLScalarValue(ASTNodeKind.FloatValue) - { - Value = token.Value, - Location = GetLocation(token.Start) - }; - } - - private ASTNode ParseFragment() - { - var comment = GetComment(); - int start = _currentToken.Start; - Expect(TokenKind.SPREAD); - + // to aid formatting when representing a longer list of possible locations + Skip(TokenKind.PIPE); + + do + { + locations.Add(ParseName()); + } + while (Skip(TokenKind.PIPE)); + + return locations; + } + + private List? ParseDirectives() + { + List? directives = null; + while (Peek(TokenKind.AT)) + (directives ??= new List()).Add(ParseDirective()); + + return directives; + } + + private GraphQLDocument ParseDocument() + { + int start = _currentToken.Start; + var definitions = ParseDefinitionsIfNotEOF(); + if (_comment != null) + { + if (_comments == null) + _comments = new List(); + _comments.Add(_comment); + } + + return CreateDocument(start, definitions, _comments); + } + + private GraphQLEnumTypeDefinition ParseEnumTypeDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("enum"); + + return new GraphQLEnumTypeDefinition + { + Comment = comment, + Name = ParseName(), + Directives = ParseDirectives(), + Values = Many(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseEnumValueDefinition(), TokenKind.BRACE_R), + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseEnumValue(Token token) + { + Advance(); + return new GraphQLScalarValue(ASTNodeKind.EnumValue) + { + Value = token.Value, + Location = GetLocation(token.Start) + }; + } + + private GraphQLEnumValueDefinition ParseEnumValueDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + + return new GraphQLEnumValueDefinition + { + Comment = comment, + Name = ParseName(), + Directives = ParseDirectives(), + Location = GetLocation(start) + }; + } + + private GraphQLFieldDefinition ParseFieldDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + var name = ParseName(); + var args = ParseArgumentDefs(); + Expect(TokenKind.COLON); + + return new GraphQLFieldDefinition + { + Comment = comment, + Name = name, + Arguments = args, + Type = ParseType(), + Directives = ParseDirectives(), + Location = GetLocation(start) + }; + } + + private GraphQLFieldSelection ParseFieldSelection() + { + var comment = GetComment(); + int start = _currentToken.Start; + var nameOrAlias = ParseName(); + GraphQLName name; + GraphQLName? alias; + + if (Skip(TokenKind.COLON)) + { + name = ParseName(); + alias = nameOrAlias; + } + else + { + alias = null; + name = nameOrAlias; + } + + return CreateFieldSelection(start, name, alias, comment); + } + + private GraphQLValue ParseFloat(/*bool isConstant*/) + { + var token = _currentToken; + Advance(); + return new GraphQLScalarValue(ASTNodeKind.FloatValue) + { + Value = token.Value, + Location = GetLocation(token.Start) + }; + } + + private ASTNode ParseFragment() + { + var comment = GetComment(); + int start = _currentToken.Start; + Expect(TokenKind.SPREAD); + return Peek(TokenKind.NAME) && !"on".Equals(_currentToken.Value) ? CreateGraphQLFragmentSpread(start, comment) - : CreateInlineFragment(start, comment); - } - - private GraphQLFragmentDefinition ParseFragmentDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("fragment"); - - return new GraphQLFragmentDefinition - { - Comment = comment, - Name = ParseFragmentName(), - TypeCondition = ExpectOnKeywordAndParseNamedType(), - Directives = ParseDirectives(), - SelectionSet = ParseSelectionSet(), - Location = GetLocation(start) - }; - } - - private GraphQLName ParseFragmentName() - { - if ("on".Equals(_currentToken.Value)) - { - throw new GraphQLSyntaxErrorException( - $"Unexpected {_currentToken}", _source, _currentToken.Start); - } - - return ParseName(); - } - - private List? ParseImplementsInterfaces() - { - List? types = null; - if (_currentToken.Value?.Equals("implements") == true) - { - types = new List(); - Advance(); - - do - { - types.Add(ParseNamedType()); - } - while (Peek(TokenKind.NAME)); - } - - return types; - } - - private GraphQLInputObjectTypeDefinition ParseInputObjectTypeDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("input"); - - return new GraphQLInputObjectTypeDefinition - { - Comment = comment, - Name = ParseName(), - Directives = ParseDirectives(), - Fields = Any(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseInputValueDef(), TokenKind.BRACE_R), - Location = GetLocation(start) - }; - } - - private GraphQLInputValueDefinition ParseInputValueDef() - { - var comment = GetComment(); - int start = _currentToken.Start; - var name = ParseName(); - Expect(TokenKind.COLON); - - return new GraphQLInputValueDefinition - { - Comment = comment, - Name = name, - Type = ParseType(), - DefaultValue = GetDefaultConstantValue(), - Directives = ParseDirectives(), - Location = GetLocation(start) - }; - } - - private GraphQLValue ParseInt(/*bool isConstant*/) - { - var token = _currentToken; - Advance(); - - return new GraphQLScalarValue(ASTNodeKind.IntValue) - { - Value = token.Value, - Location = GetLocation(token.Start) - }; - } - - private GraphQLInterfaceTypeDefinition ParseInterfaceTypeDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("interface"); - - return new GraphQLInterfaceTypeDefinition - { - Comment = comment, - Name = ParseName(), - Directives = ParseDirectives(), - Fields = Any(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseFieldDefinition(), TokenKind.BRACE_R), - Location = GetLocation(start) - }; - } - - private GraphQLValue ParseList(bool isConstant) - { + : CreateInlineFragment(start, comment); + } + + private GraphQLFragmentDefinition ParseFragmentDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("fragment"); + + return new GraphQLFragmentDefinition + { + Comment = comment, + Name = ParseFragmentName(), + TypeCondition = ExpectOnKeywordAndParseNamedType(), + Directives = ParseDirectives(), + SelectionSet = ParseSelectionSet(), + Location = GetLocation(start) + }; + } + + private GraphQLName ParseFragmentName() + { + if ("on".Equals(_currentToken.Value)) + { + throw new GraphQLSyntaxErrorException( + $"Unexpected {_currentToken}", _source, _currentToken.Start); + } + + return ParseName(); + } + + private List? ParseImplementsInterfaces() + { + List? types = null; + if (_currentToken.Value?.Equals("implements") == true) + { + types = new List(); + Advance(); + + do + { + types.Add(ParseNamedType()); + } + while (Peek(TokenKind.NAME)); + } + + return types; + } + + private GraphQLInputObjectTypeDefinition ParseInputObjectTypeDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("input"); + + return new GraphQLInputObjectTypeDefinition + { + Comment = comment, + Name = ParseName(), + Directives = ParseDirectives(), + Fields = Any(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseInputValueDef(), TokenKind.BRACE_R), + Location = GetLocation(start) + }; + } + + private GraphQLInputValueDefinition ParseInputValueDef() + { + var comment = GetComment(); + int start = _currentToken.Start; + var name = ParseName(); + Expect(TokenKind.COLON); + + return new GraphQLInputValueDefinition + { + Comment = comment, + Name = name, + Type = ParseType(), + DefaultValue = GetDefaultConstantValue(), + Directives = ParseDirectives(), + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseInt(/*bool isConstant*/) + { + var token = _currentToken; + Advance(); + + return new GraphQLScalarValue(ASTNodeKind.IntValue) + { + Value = token.Value, + Location = GetLocation(token.Start) + }; + } + + private GraphQLInterfaceTypeDefinition ParseInterfaceTypeDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("interface"); + + return new GraphQLInterfaceTypeDefinition + { + Comment = comment, + Name = ParseName(), + Directives = ParseDirectives(), + Fields = Any(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseFieldDefinition(), TokenKind.BRACE_R), + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseList(bool isConstant) + { int start = _currentToken.Start; // the compiler caches these delegates in the generated code ParseCallback constant = (ref ParserContext context) => context.ParseConstantValue(); - ParseCallback value = (ref ParserContext context) => context.ParseValueValue(); - - return new GraphQLListValue(ASTNodeKind.ListValue) - { - Values = Any(TokenKind.BRACKET_L, isConstant ? constant : value, TokenKind.BRACKET_R), - Location = GetLocation(start), - AstValue = _source.Body.Substring(start, _currentToken.End - start - 1) - }; - } - - private GraphQLName ParseName() - { - int start = _currentToken.Start; - string? value = _currentToken.Value; - - Expect(TokenKind.NAME); - - return new GraphQLName - { - Location = GetLocation(start), - Value = value - }; - } - - private ASTNode? ParseNamedDefinition() - { - return _currentToken.Value switch - { + ParseCallback value = (ref ParserContext context) => context.ParseValueValue(); + + return new GraphQLListValue(ASTNodeKind.ListValue) + { + Values = Any(TokenKind.BRACKET_L, isConstant ? constant : value, TokenKind.BRACKET_R), + Location = GetLocation(start), + AstValue = _source.Body.Substring(start, _currentToken.End - start - 1) + }; + } + + private GraphQLName ParseName() + { + int start = _currentToken.Start; + string? value = _currentToken.Value; + + Expect(TokenKind.NAME); + + return new GraphQLName + { + Location = GetLocation(start), + Value = value + }; + } + + private ASTNode? ParseNamedDefinition() + { + return _currentToken.Value switch + { "query" => ParseOperationDefinition(), "mutation" => ParseOperationDefinition(), "subscription" => ParseOperationDefinition(), - "fragment" => ParseFragmentDefinition(), - "schema" => ParseSchemaDefinition(), - "scalar" => ParseScalarTypeDefinition(), - "type" => ParseObjectTypeDefinition(), - "interface" => ParseInterfaceTypeDefinition(), - "union" => ParseUnionTypeDefinition(), - "enum" => ParseEnumTypeDefinition(), - "input" => ParseInputObjectTypeDefinition(), - "extend" => ParseTypeExtensionDefinition(), + "fragment" => ParseFragmentDefinition(), + "schema" => ParseSchemaDefinition(), + "scalar" => ParseScalarTypeDefinition(), + "type" => ParseObjectTypeDefinition(), + "interface" => ParseInterfaceTypeDefinition(), + "union" => ParseUnionTypeDefinition(), + "enum" => ParseEnumTypeDefinition(), + "input" => ParseInputObjectTypeDefinition(), + "extend" => ParseTypeExtensionDefinition(), "directive" => ParseDirectiveDefinition(), - _ => null - }; - } - - private GraphQLNamedType ParseNamedType() - { - int start = _currentToken.Start; - return new GraphQLNamedType - { - Name = ParseName(), - Location = GetLocation(start) - }; - } - - private GraphQLValue ParseNameValue(/*bool isConstant*/) - { - var token = _currentToken; - + _ => null + }; + } + + private GraphQLNamedType ParseNamedType() + { + int start = _currentToken.Start; + return new GraphQLNamedType + { + Name = ParseName(), + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseNameValue(/*bool isConstant*/) + { + var token = _currentToken; + if ("true".Equals(token.Value) || "false".Equals(token.Value)) { return ParseBooleanValue(token); } - else if (token.Value != null) + else if (token.Value != null) { return token.Value.Equals("null") ? ParseNullValue(token) : ParseEnumValue(token); - } - - throw new GraphQLSyntaxErrorException( - $"Unexpected {_currentToken}", _source, _currentToken.Start); - } - - private GraphQLValue ParseObject(bool isConstant) - { - var comment = GetComment(); - int start = _currentToken.Start; - - return new GraphQLObjectValue - { - Comment = comment, - Fields = ParseObjectFields(isConstant), - Location = GetLocation(start) - }; - } - - private GraphQLValue ParseNullValue(Token token) - { - Advance(); - return new GraphQLScalarValue(ASTNodeKind.NullValue) - { - Value = null, - Location = GetLocation(token.Start) - }; - } - - private GraphQLObjectField ParseObjectField(bool isConstant) - { - var comment = GetComment(); - int start = _currentToken.Start; - return new GraphQLObjectField - { - Comment = comment, - Name = ParseName(), - Value = ExpectColonAndParseValueLiteral(isConstant), - Location = GetLocation(start) - }; - } - - private List ParseObjectFields(bool isConstant) - { - var fields = new List(); - - Expect(TokenKind.BRACE_L); - while (!Skip(TokenKind.BRACE_R)) - fields.Add(ParseObjectField(isConstant)); - - return fields; - } - - private GraphQLObjectTypeDefinition ParseObjectTypeDefinition() - { - var comment = GetComment(); - - int start = _currentToken.Start; - ExpectKeyword("type"); - - return new GraphQLObjectTypeDefinition - { - Comment = comment, - Name = ParseName(), - Interfaces = ParseImplementsInterfaces(), - Directives = ParseDirectives(), - Fields = Any(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseFieldDefinition(), TokenKind.BRACE_R), - Location = GetLocation(start) - }; - } - - private ASTNode ParseOperationDefinition() - { - int start = _currentToken.Start; - - return Peek(TokenKind.BRACE_L) - ? CreateOperationDefinition(start) - : CreateOperationDefinition(start, ParseOperationType(), GetName()); - } - - private OperationType ParseOperationType() - { - var token = _currentToken; - Expect(TokenKind.NAME); - - return token.Value switch - { - "mutation" => OperationType.Mutation, + } + + throw new GraphQLSyntaxErrorException( + $"Unexpected {_currentToken}", _source, _currentToken.Start); + } + + private GraphQLValue ParseObject(bool isConstant) + { + var comment = GetComment(); + int start = _currentToken.Start; + + return new GraphQLObjectValue + { + Comment = comment, + Fields = ParseObjectFields(isConstant), + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseNullValue(Token token) + { + Advance(); + return new GraphQLScalarValue(ASTNodeKind.NullValue) + { + Value = null, + Location = GetLocation(token.Start) + }; + } + + private GraphQLObjectField ParseObjectField(bool isConstant) + { + var comment = GetComment(); + int start = _currentToken.Start; + return new GraphQLObjectField + { + Comment = comment, + Name = ParseName(), + Value = ExpectColonAndParseValueLiteral(isConstant), + Location = GetLocation(start) + }; + } + + private List ParseObjectFields(bool isConstant) + { + var fields = new List(); + + Expect(TokenKind.BRACE_L); + while (!Skip(TokenKind.BRACE_R)) + fields.Add(ParseObjectField(isConstant)); + + return fields; + } + + private GraphQLObjectTypeDefinition ParseObjectTypeDefinition() + { + var comment = GetComment(); + + int start = _currentToken.Start; + ExpectKeyword("type"); + + return new GraphQLObjectTypeDefinition + { + Comment = comment, + Name = ParseName(), + Interfaces = ParseImplementsInterfaces(), + Directives = ParseDirectives(), + Fields = Any(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseFieldDefinition(), TokenKind.BRACE_R), + Location = GetLocation(start) + }; + } + + private ASTNode ParseOperationDefinition() + { + int start = _currentToken.Start; + + return Peek(TokenKind.BRACE_L) + ? CreateOperationDefinition(start) + : CreateOperationDefinition(start, ParseOperationType(), GetName()); + } + + private OperationType ParseOperationType() + { + var token = _currentToken; + Expect(TokenKind.NAME); + + return token.Value switch + { + "mutation" => OperationType.Mutation, "subscription" => OperationType.Subscription, - _ => OperationType.Query - }; - } - - private GraphQLOperationTypeDefinition ParseOperationTypeDefinition() - { - int start = _currentToken.Start; - var operation = ParseOperationType(); - Expect(TokenKind.COLON); - var type = ParseNamedType(); - - return new GraphQLOperationTypeDefinition - { - Operation = operation, - Type = type, - Location = GetLocation(start) - }; - } - - private GraphQLScalarTypeDefinition ParseScalarTypeDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("scalar"); - var name = ParseName(); - var directives = ParseDirectives(); - - return new GraphQLScalarTypeDefinition - { - Comment = comment, - Name = name, - Directives = directives, - Location = GetLocation(start) - }; - } - - private GraphQLSchemaDefinition ParseSchemaDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("schema"); - var directives = ParseDirectives(); - var operationTypes = Many(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseOperationTypeDefinition(), TokenKind.BRACE_R); - - return new GraphQLSchemaDefinition - { - Comment = comment, - Directives = directives, - OperationTypes = operationTypes, - Location = GetLocation(start) - }; - } - - private ASTNode ParseSelection() - { - return Peek(TokenKind.SPREAD) ? - ParseFragment() : - ParseFieldSelection(); - } - - private GraphQLSelectionSet ParseSelectionSet() - { - int start = _currentToken.Start; - return new GraphQLSelectionSet - { - Selections = Many(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseSelection(), TokenKind.BRACE_R), - Location = GetLocation(start) - }; - } - - private GraphQLValue ParseString(/*bool isConstant*/) - { - var token = _currentToken; - Advance(); - return new GraphQLScalarValue(ASTNodeKind.StringValue) - { - Value = token.Value, - Location = GetLocation(token.Start) - }; - } - - private GraphQLType ParseType() - { - GraphQLType type; - int start = _currentToken.Start; - if (Skip(TokenKind.BRACKET_L)) - { - type = ParseType(); - Expect(TokenKind.BRACKET_R); - type = new GraphQLListType - { - Type = type, - Location = GetLocation(start) - }; - } - else - { - type = ParseNamedType(); - } - + _ => OperationType.Query + }; + } + + private GraphQLOperationTypeDefinition ParseOperationTypeDefinition() + { + int start = _currentToken.Start; + var operation = ParseOperationType(); + Expect(TokenKind.COLON); + var type = ParseNamedType(); + + return new GraphQLOperationTypeDefinition + { + Operation = operation, + Type = type, + Location = GetLocation(start) + }; + } + + private GraphQLScalarTypeDefinition ParseScalarTypeDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("scalar"); + var name = ParseName(); + var directives = ParseDirectives(); + + return new GraphQLScalarTypeDefinition + { + Comment = comment, + Name = name, + Directives = directives, + Location = GetLocation(start) + }; + } + + private GraphQLSchemaDefinition ParseSchemaDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("schema"); + var directives = ParseDirectives(); + var operationTypes = Many(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseOperationTypeDefinition(), TokenKind.BRACE_R); + + return new GraphQLSchemaDefinition + { + Comment = comment, + Directives = directives, + OperationTypes = operationTypes, + Location = GetLocation(start) + }; + } + + private ASTNode ParseSelection() + { + return Peek(TokenKind.SPREAD) ? + ParseFragment() : + ParseFieldSelection(); + } + + private GraphQLSelectionSet ParseSelectionSet() + { + int start = _currentToken.Start; + return new GraphQLSelectionSet + { + Selections = Many(TokenKind.BRACE_L, (ref ParserContext context) => context.ParseSelection(), TokenKind.BRACE_R), + Location = GetLocation(start) + }; + } + + private GraphQLValue ParseString(/*bool isConstant*/) + { + var token = _currentToken; + Advance(); + return new GraphQLScalarValue(ASTNodeKind.StringValue) + { + Value = token.Value, + Location = GetLocation(token.Start) + }; + } + + private GraphQLType ParseType() + { + GraphQLType type; + int start = _currentToken.Start; + if (Skip(TokenKind.BRACKET_L)) + { + type = ParseType(); + Expect(TokenKind.BRACKET_R); + type = new GraphQLListType + { + Type = type, + Location = GetLocation(start) + }; + } + else + { + type = ParseNamedType(); + } + return Skip(TokenKind.BANG) - ? new GraphQLNonNullType - { - Type = type, - Location = GetLocation(start) + ? new GraphQLNonNullType + { + Type = type, + Location = GetLocation(start) } - : type; - } - - private GraphQLTypeExtensionDefinition ParseTypeExtensionDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("extend"); - var definition = ParseObjectTypeDefinition(); - - return new GraphQLTypeExtensionDefinition - { - Comment = comment, - Name = definition.Name, - Definition = definition, - Location = GetLocation(start) - }; - } - - private List ParseUnionMembers() - { + : type; + } + + private GraphQLTypeExtensionDefinition ParseTypeExtensionDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("extend"); + var definition = ParseObjectTypeDefinition(); + + return new GraphQLTypeExtensionDefinition + { + Comment = comment, + Name = definition.Name, + Definition = definition, + Location = GetLocation(start) + }; + } + + private List ParseUnionMembers() + { var members = new List(); // Union members may be defined with an optional leading | character // to aid formatting when representing a longer list of possible types - Skip(TokenKind.PIPE); - - do - { - members.Add(ParseNamedType()); - } - while (Skip(TokenKind.PIPE)); - - return members; - } - - private GraphQLUnionTypeDefinition ParseUnionTypeDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - ExpectKeyword("union"); - var name = ParseName(); - var directives = ParseDirectives(); - Expect(TokenKind.EQUALS); - var types = ParseUnionMembers(); - - return new GraphQLUnionTypeDefinition - { - Comment = comment, - Name = name, - Directives = directives, - Types = types, - Location = GetLocation(start) - }; - } - + Skip(TokenKind.PIPE); + + do + { + members.Add(ParseNamedType()); + } + while (Skip(TokenKind.PIPE)); + + return members; + } + + private GraphQLUnionTypeDefinition ParseUnionTypeDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + ExpectKeyword("union"); + var name = ParseName(); + var directives = ParseDirectives(); + Expect(TokenKind.EQUALS); + var types = ParseUnionMembers(); + + return new GraphQLUnionTypeDefinition + { + Comment = comment, + Name = name, + Directives = directives, + Types = types, + Location = GetLocation(start) + }; + } + private GraphQLValue ParseValueLiteral(bool isConstant) => _currentToken.Kind switch { TokenKind.BRACKET_L => ParseList(isConstant), @@ -992,60 +1004,60 @@ private GraphQLUnionTypeDefinition ParseUnionTypeDefinition() TokenKind.NAME => ParseNameValue(/*isConstant*/), TokenKind.DOLLAR when !isConstant => ParseVariable(), _ => throw new GraphQLSyntaxErrorException($"Unexpected {_currentToken}", _source, _currentToken.Start) - }; - - private GraphQLValue ParseValueValue() => ParseValueLiteral(false); - - private GraphQLVariable ParseVariable() - { - int start = _currentToken.Start; - Expect(TokenKind.DOLLAR); - - return new GraphQLVariable - { - Name = GetName(), - Location = GetLocation(start) - }; - } - - private GraphQLVariableDefinition ParseVariableDefinition() - { - var comment = GetComment(); - int start = _currentToken.Start; - - return new GraphQLVariableDefinition - { - Comment = comment, - Variable = ParseVariable(), - Type = AdvanceThroughColonAndParseType(), - DefaultValue = SkipEqualsAndParseValueLiteral(), - Location = GetLocation(start) - }; - } - - private List? ParseVariableDefinitions() - { - return Peek(TokenKind.PAREN_L) ? - Many(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseVariableDefinition(), TokenKind.PAREN_R) : - null; - } - - private bool Peek(TokenKind kind) => _currentToken.Kind == kind; - - private bool Skip(TokenKind kind) - { - ParseComment(); - - bool isCurrentTokenMatching = _currentToken.Kind == kind; - - if (isCurrentTokenMatching) - { - Advance(); - } - - return isCurrentTokenMatching; - } - - private object? SkipEqualsAndParseValueLiteral() => Skip(TokenKind.EQUALS) ? ParseValueLiteral(true) : null; - } -} + }; + + private GraphQLValue ParseValueValue() => ParseValueLiteral(false); + + private GraphQLVariable ParseVariable() + { + int start = _currentToken.Start; + Expect(TokenKind.DOLLAR); + + return new GraphQLVariable + { + Name = GetName(), + Location = GetLocation(start) + }; + } + + private GraphQLVariableDefinition ParseVariableDefinition() + { + var comment = GetComment(); + int start = _currentToken.Start; + + return new GraphQLVariableDefinition + { + Comment = comment, + Variable = ParseVariable(), + Type = AdvanceThroughColonAndParseType(), + DefaultValue = SkipEqualsAndParseValueLiteral(), + Location = GetLocation(start) + }; + } + + private List? ParseVariableDefinitions() + { + return Peek(TokenKind.PAREN_L) ? + Many(TokenKind.PAREN_L, (ref ParserContext context) => context.ParseVariableDefinition(), TokenKind.PAREN_R) : + null; + } + + private bool Peek(TokenKind kind) => _currentToken.Kind == kind; + + private bool Skip(TokenKind kind) + { + ParseComment(); + + bool isCurrentTokenMatching = _currentToken.Kind == kind; + + if (isCurrentTokenMatching) + { + Advance(); + } + + return isCurrentTokenMatching; + } + + private object? SkipEqualsAndParseValueLiteral() => Skip(TokenKind.EQUALS) ? ParseValueLiteral(true) : null; + } +}