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