/ lib / graphlyte / parser.rb
parser.rb
  1  # frozen_string_literal: true
  2  
  3  require_relative './errors'
  4  require_relative './syntax'
  5  require_relative './document'
  6  require_relative './parsing/backtracking_parser'
  7  
  8  module Graphlyte
  9    # A parser of GraphQL documents from a stream of lexical tokens.
 10    class Parser < Parsing::BacktrackingParser
 11      def document
 12        doc = Graphlyte::Document.new
 13        doc.definitions = some { definition }
 14  
 15        expect(:EOF)
 16  
 17        doc
 18      end
 19  
 20      # Restricted parser: only parses executable definitions
 21      def query
 22        doc = Graphlyte::Document.new
 23        doc.definitions = some { executable_definition }
 24  
 25        expect(:EOF)
 26  
 27        doc
 28      end
 29  
 30      def definition
 31        one_of(:executable_definition, :type_definition)
 32      end
 33  
 34      def executable_definition
 35        one_of(:fragment, :operation)
 36      end
 37  
 38      def operation
 39        t = next_token
 40  
 41        case t.type
 42        when :PUNCTUATOR
 43          @index -= 1
 44          implicit_query
 45        when :NAME
 46          operation_from_kind(t.value.to_sym)
 47        else
 48          raise Unexpected, t
 49        end
 50      end
 51  
 52      def implicit_query
 53        Graphlyte::Syntax::Operation.new(type: :query, selection: selection_set)
 54      end
 55  
 56      def operation_from_kind(kind)
 57        op = Graphlyte::Syntax::Operation.new
 58  
 59        try_parse do
 60          op.type = kind
 61          op.name = optional { name }
 62          op.variables = optional { variable_definitions }
 63          op.directives = directives
 64          op.selection = selection_set
 65        end
 66  
 67        op
 68      end
 69  
 70      def selection_set
 71        bracket('{', '}') do
 72          some { one_of(:inline_fragment, :fragment_spread, :field_selection) }
 73        end
 74      end
 75  
 76      def fragment_spread
 77        frag = Graphlyte::Syntax::FragmentSpread.new
 78  
 79        punctuator('...')
 80        frag.name = name
 81        frag.directives = directives
 82  
 83        frag
 84      end
 85  
 86      def inline_fragment
 87        punctuator('...')
 88        name('on')
 89  
 90        frag = Graphlyte::Syntax::InlineFragment.new
 91  
 92        frag.type_name = name
 93        frag.directives = directives
 94        frag.selection = selection_set
 95  
 96        frag
 97      end
 98  
 99      def field_selection
100        field = Graphlyte::Syntax::Field.new
101  
102        field.as = optional do
103          n = name
104          punctuator(':')
105  
106          n
107        end
108  
109        field.name = name
110        field.arguments = optional_list { arguments }
111        field.directives = directives
112        field.selection = optional_list { selection_set }
113  
114        field
115      end
116  
117      def arguments
118        bracket('(', ')') { some { parse_argument } }
119      end
120  
121      def parse_argument
122        arg = Graphlyte::Syntax::Argument.new
123  
124        arg.name = name
125        expect(:PUNCTUATOR, ':')
126        arg.value = parse_value
127  
128        arg
129      end
130  
131      def parse_value
132        t = next_token
133  
134        case t.type
135        when :STRING, :NUMBER
136          Graphlyte::Syntax::Value.new(t.value, t.type)
137        when :NAME
138          Graphlyte::Syntax::Value.from_name(t.value)
139        when :PUNCTUATOR
140          case t.value
141          when '$'
142            Graphlyte::Syntax::VariableReference.new(name)
143          when '{'
144            @index -= 1
145            parse_object_value
146          when '['
147            @index -= 1
148            parse_array_value
149          else
150            raise Unexpected, t
151          end
152        else
153          raise Unexpected, t
154        end
155      end
156  
157      def parse_array_value
158        bracket('[', ']') { many { parse_value } }
159      end
160  
161      def parse_object_value
162        bracket('{', '}') do
163          many do
164            n = name
165            expect(:PUNCTUATOR, ':')
166            value = parse_value
167  
168            [n, value]
169          end.to_h
170        end
171      end
172  
173      def directives
174        ret = []
175        while peek(offset: 1).punctuator?('@')
176          d = Graphlyte::Syntax::Directive.new
177  
178          expect(:PUNCTUATOR, '@')
179          d.name = name
180          d.arguments = optional { arguments }
181  
182          ret << d
183        end
184  
185        ret
186      end
187  
188      def operation_type
189        raise Unexpected, current unless current.type == :NAME
190  
191        current.value.to_sym
192      end
193  
194      def variable_definitions
195        bracket('(', ')') do
196          some do
197            var = Graphlyte::Syntax::VariableDefinition.new
198  
199            var.variable = variable_name
200            expect(:PUNCTUATOR, ':')
201            var.type = one_of(:list_type_name, :type_name)
202  
203            var.default_value = optional { default_value }
204            var.directives = directives
205  
206            var
207          end
208        end
209      end
210  
211      def default_value
212        expect(:PUNCTUATOR, '=')
213  
214        parse_value
215      end
216  
217      def variable_name
218        expect(:PUNCTUATOR, '$')
219  
220        name
221      end
222  
223      def type_name(list: false)
224        ty = Graphlyte::Syntax::Type.new(name)
225  
226        t = peek(offset: 1)
227        ty.non_null = t.punctuator?('!')
228        ty.is_list = list
229        advance if ty.non_null
230  
231        ty
232      end
233  
234      def type_name!
235        ty = type_name
236        expect(:EOF)
237  
238        ty
239      end
240  
241      def list_type_name
242        type = bracket('[', ']') { type_name(list: true) }
243        t = peek(offset: 1)
244        type.non_null_list = t.punctuator?('!')
245        advance if type.non_null_list
246  
247        type
248      end
249  
250      def fragment
251        frag = Graphlyte::Syntax::Fragment.new
252  
253        expect(:NAME, 'fragment')
254        frag.name = name
255  
256        expect(:NAME, 'on')
257  
258        frag.type_name = name
259        frag.directives = directives
260        frag.selection = selection_set
261  
262        frag
263      end
264  
265      def type_definition
266        raise ParseError, "TODO: #{current.location}"
267      end
268    end
269  end