Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from string import StringReader 

2 

3class GraphqlError(Error): 

4 message: string 

5 

6trait _Token: 

7 pass 

8 

9class _Punctuator(_Token): 

10 value: char 

11 

12class _Dots(_Token): 

13 pass 

14 

15class _Name(_Token): 

16 value: string 

17 

18class _IntValue(_Token): 

19 value: string 

20 

21class _FloatValue(_Token): 

22 value: string 

23 

24class _StringValue(_Token): 

25 value: string 

26 

27class _End(_Token): 

28 pass 

29 

30class _Tokens: 

31 _tokens: [_Token] 

32 _pos: i64 

33 

34 func __init__(self, tokens: [_Token]): 

35 self._tokens = tokens 

36 self._pos = 0 

37 

38 func available(self) -> bool: 

39 return self._pos < self._tokens.length() 

40 

41 func get(self) -> _Token: 

42 if not self.available(): 

43 raise GraphqlError("Out of tokens.") 

44 

45 self._pos += 1 

46 

47 return self._tokens[self._pos - 1] 

48 

49 func peek(self) -> _Token: 

50 if not self.available(): 

51 raise GraphqlError("Out of tokens.") 

52 

53 return self._tokens[self._pos] 

54 

55 func unget(self): 

56 self._pos -= 1 

57 

58class Document: 

59 definitions: [Definition] 

60 

61class Definition: 

62 executable_definition: ExecutableDefinition 

63 

64class ExecutableDefinition: 

65 operation_definition: OperationDefinition 

66 

67class OperationDefinition: 

68 operation_type: string? 

69 name: string? 

70 variable_definitions: [VariableDefinition]? 

71 selections: [Selection] 

72 

73class VariableDefinition: 

74 name: string 

75 value: Type 

76 

77class Type: 

78 name: string 

79 types: [Type]? 

80 non_null: bool 

81 

82class Selection: 

83 field: Field 

84 

85class Field: 

86 name: string 

87 arguments: [Argument]? 

88 selections: [Selection]? 

89 

90class Argument: 

91 name: string 

92 value: string? 

93 variable: string? 

94 

95func _tokenize_name(reader: StringReader) -> _Token: 

96 name = "" 

97 

98 while True: 

99 ch = reader.get() 

100 

101 if ch.is_alpha() or ch.is_digit() or ch == '_': 

102 name += ch 

103 else: 

104 if ch != '': 

105 reader.unget() 

106 

107 break 

108 

109 return _Name(name) 

110 

111func _tokenize_number(reader: StringReader) -> _Token: 

112 value = "" 

113 

114 while True: 

115 ch = reader.get() 

116 

117 if ch.is_digit(): 

118 value += ch 

119 else: 

120 if ch != '': 

121 reader.unget() 

122 

123 break 

124 

125 return _IntValue(value) 

126 

127func _tokenize_dots(reader: StringReader) -> _Token: 

128 for _ in range(3): 

129 ch = reader.get() 

130 

131 if ch != '.': 

132 raise GraphqlError("No '.'.") 

133 

134 return _Dots() 

135 

136func _tokenize_string(reader: StringReader) -> _Token: 

137 value = "" 

138 

139 while True: 

140 ch = reader.get() 

141 

142 if ch == '\"': 

143 break 

144 elif ch == '': 

145 raise GraphqlError("No end of string.") 

146 else: 

147 value += ch 

148 

149 return _StringValue(value) 

150 

151func _tokenize(document: string) -> _Tokens: 

152 reader = StringReader(document) 

153 token: _Token? = None 

154 tokens: [_Token] = [] 

155 

156 while True: 

157 ch = reader.get() 

158 

159 if ch in ",\t\r\n ": 

160 continue 

161 elif ch in "!$&():=@[]{|}": 

162 token = _Punctuator(ch) 

163 elif ch == '.': 

164 reader.unget() 

165 token = _tokenize_dots(reader) 

166 elif ch == '-' or ch.is_digit(): 

167 reader.unget() 

168 token = _tokenize_number(reader) 

169 elif ch == '\"': 

170 token = _tokenize_string(reader) 

171 elif ch.is_alpha() or ch == '_': 

172 reader.unget() 

173 token = _tokenize_name(reader) 

174 elif ch == '': 

175 break 

176 else: 

177 raise GraphqlError(f"Invalid character '{ch}'") 

178 

179 tokens.append(token) 

180 

181 # tokens.append(_End()) 

182 

183 return _Tokens(tokens) 

184 

185func _parse_arguments(tokens: _Tokens) -> [Argument]?: 

186 match tokens.peek(): 

187 case _Punctuator(value='('): 

188 pass 

189 case _: 

190 return None 

191 

192 tokens.get() 

193 arguments: [Argument]= [] 

194 

195 while True: 

196 match tokens.get(): 

197 case _Name() as name_token: 

198 _parse_colon(tokens) 

199 

200 value: string? = None 

201 variable: string? = None 

202 

203 match tokens.get(): 

204 case _StringValue() as string_value_token: 

205 value = string_value_token.value 

206 case _IntValue() as int_value_token: 

207 pass 

208 case _Punctuator(value='$'): 

209 variable = _parse_variable(tokens) 

210 case _: 

211 raise GraphqlError("No value.") 

212 

213 arguments.append(Argument(name_token.value, value, variable)) 

214 case _Punctuator(value=')'): 

215 break 

216 case _: 

217 raise GraphqlError("No ).") 

218 

219 return arguments 

220 

221func _parse_name(tokens: _Tokens) -> string?: 

222 match tokens.get(): 

223 case _Name() as name_token: 

224 return name_token.value 

225 case _: 

226 tokens.unget() 

227 

228 return None 

229 

230func _parse_field(tokens: _Tokens) -> Field?: 

231 name = _parse_name(tokens) 

232 

233 if name is None: 

234 return None 

235 

236 arguments = _parse_arguments(tokens) 

237 selections = _parse_selection_set(tokens) 

238 

239 return Field(name, arguments, selections) 

240 

241func _parse_selection(tokens: _Tokens) -> Selection?: 

242 field = _parse_field(tokens) 

243 

244 if field is None: 

245 return None 

246 

247 return Selection(field) 

248 

249func _parse_selection_set(tokens: _Tokens) -> [Selection]?: 

250 selections: [Selection] = [] 

251 

252 match tokens.peek(): 

253 case _Punctuator(value='{'): 

254 pass 

255 case _: 

256 return None 

257 

258 tokens.get() 

259 

260 while True: 

261 match tokens.peek(): 

262 case _Punctuator(value='}'): 

263 break 

264 

265 selection = _parse_selection(tokens) 

266 

267 if selection is None: 

268 return None 

269 

270 selections.append(selection) 

271 

272 tokens.get() 

273 

274 return selections 

275 

276func _parse_operation_type(tokens: _Tokens) -> string?: 

277 operation_type = _parse_name(tokens) 

278 

279 if operation_type is None: 

280 return None 

281 

282 if operation_type not in ["query", "mutation", "subscription"]: 

283 raise GraphqlError(f"Bad operation '{operation_type}'.") 

284 

285 return operation_type 

286 

287func _parse_non_null(tokens: _Tokens) -> bool: 

288 match tokens.get(): 

289 case _Punctuator(value='!'): 

290 return True 

291 case _: 

292 tokens.unget() 

293 

294 return False 

295 

296 

297func _parse_type(tokens: _Tokens) -> Type: 

298 match tokens.get(): 

299 case _Name() as name_token: 

300 non_null = _parse_non_null(tokens) 

301 

302 return Type(name_token.value, None, non_null) 

303 case _: 

304 raise GraphqlError("No type name.") 

305 

306func _parse_variable(tokens: _Tokens) -> string: 

307 match tokens.get(): 

308 case _Name() as name_token: 

309 return name_token.value 

310 case _: 

311 raise GraphqlError("No variable name.") 

312 

313func _parse_colon(tokens: _Tokens): 

314 match tokens.get(): 

315 case _Punctuator(value=':'): 

316 pass 

317 case _: 

318 raise GraphqlError("No :.") 

319 

320func _parse_variable_definitions(tokens: _Tokens) -> [VariableDefinition]?: 

321 match tokens.peek(): 

322 case _Punctuator(value='('): 

323 pass 

324 case _: 

325 return None 

326 

327 tokens.get() 

328 variable_definitions: [VariableDefinition] = [] 

329 

330 while True: 

331 match tokens.get(): 

332 case _Punctuator(value='$'): 

333 name = _parse_variable(tokens) 

334 _parse_colon(tokens) 

335 type = _parse_type(tokens) 

336 variable_definitions.append(VariableDefinition(name, type)) 

337 case _Punctuator(value=')'): 

338 break 

339 case _: 

340 raise GraphqlError("No ).") 

341 

342 return variable_definitions 

343 

344func _parse_operation_definition(tokens: _Tokens) -> OperationDefinition?: 

345 name: string? = None 

346 variable_definitions: [VariableDefinition]? = None 

347 operation_type = _parse_operation_type(tokens) 

348 

349 if operation_type is not None: 

350 name = _parse_name(tokens) 

351 

352 if name is not None: 

353 variable_definitions = _parse_variable_definitions(tokens) 

354 

355 selections = _parse_selection_set(tokens) 

356 

357 if selections is None: 

358 return None 

359 

360 return OperationDefinition(operation_type, 

361 name, 

362 variable_definitions, 

363 selections) 

364 

365func _parse_executable_definition(tokens: _Tokens) -> ExecutableDefinition?: 

366 operation_definition = _parse_operation_definition(tokens) 

367 

368 if operation_definition is None: 

369 return None 

370 

371 return ExecutableDefinition(operation_definition) 

372 

373func _parse_definition(tokens: _Tokens) -> Definition?: 

374 executable_definition = _parse_executable_definition(tokens) 

375 

376 if executable_definition is None: 

377 return None 

378 

379 return Definition(executable_definition) 

380 

381func _parse_document(tokens: _Tokens) -> Document: 

382 document = Document([]) 

383 

384 while tokens.available(): 

385 definition = _parse_definition(tokens) 

386 

387 if definition is None: 

388 raise GraphqlError("Bad definition.") 

389 

390 document.definitions.append(definition) 

391 

392 return document 

393 

394func parse(document: string) -> Document: 

395 """Parse given document. 

396 

397 """ 

398 

399 tokens = _tokenize(document) 

400 

401 return _parse_document(tokens) 

402 

403test parse_query(): 

404 document = parse("\t\r" 

405 "{\n" 

406 " foo(id: 10, name: \"kalle\") {\n" 

407 " id\n" 

408 " name\n" 

409 " value\n" 

410 " }\n" 

411 "}") 

412 assert document.definitions.length() == 1 

413 selections = (document 

414 .definitions[0] 

415 .executable_definition 

416 .operation_definition 

417 .selections) 

418 assert selections.length() == 1 

419 selection = selections[0] 

420 assert selection.field.name == "foo" 

421 arguments = selection.field.arguments 

422 assert arguments.length() == 2 

423 assert arguments[0].name == "id" 

424 assert arguments[1].name == "name" 

425 selections = selection.field.selections 

426 assert selections.length() == 3 

427 assert selections[0].field.name == "id" 

428 assert selections[0].field.arguments is None 

429 assert selections[0].field.selections is None 

430 assert selections[1].field.name == "name" 

431 assert selections[1].field.arguments is None 

432 assert selections[1].field.selections is None 

433 assert selections[2].field.name == "value" 

434 assert selections[2].field.arguments is None 

435 assert selections[2].field.selections is None 

436 

437test spacex(): 

438 document = parse("{" 

439 " launchesPast(limit: 10) {" 

440 " mission_name" 

441 " launch_date_local" 

442 " launch_site {" 

443 " site_name_long" 

444 " }" 

445 " ships {" 

446 " name" 

447 " home_port" 

448 " image" 

449 " }" 

450 " }" 

451 "}") 

452 assert document.definitions.length() == 1 

453 selections = (document 

454 .definitions[0] 

455 .executable_definition 

456 .operation_definition 

457 .selections) 

458 assert selections.length() == 1 

459 assert selections[0].field.name == "launchesPast" 

460 selections = selections[0].field.selections 

461 assert selections[0].field.name == "mission_name" 

462 assert selections[1].field.name == "launch_date_local" 

463 assert selections[2].field.name == "launch_site" 

464 assert selections[2].field.selections[0].field.name == "site_name_long" 

465 assert selections[3].field.name == "ships" 

466 assert selections[3].field.selections[0].field.name == "name" 

467 assert selections[3].field.selections[1].field.name == "home_port" 

468 assert selections[3].field.selections[2].field.name == "image" 

469 

470test bad_definition(): 

471 try: 

472 parse("{{name}}") 

473 assert False 

474 except GraphqlError as error: 

475 assert error.message == "Bad definition." 

476 

477 try: 

478 parse("{") 

479 assert False 

480 except GraphqlError as error: 

481 assert error.message == "Out of tokens." 

482 

483test name(): 

484 document = parse("{foo_2}") 

485 assert document.definitions.length() == 1 

486 selections = (document 

487 .definitions[0] 

488 .executable_definition 

489 .operation_definition 

490 .selections) 

491 assert selections.length() == 1 

492 assert selections[0].field.name == "foo_2" 

493 

494test operation_type(): 

495 document = parse("query {\n" 

496 " packages {\n" 

497 " name\n" 

498 " }\n" 

499 "}") 

500 assert document.definitions.length() == 1 

501 operation_definition = (document 

502 .definitions[0] 

503 .executable_definition 

504 .operation_definition) 

505 assert operation_definition.operation_type == "query" 

506 assert operation_definition.name is None 

507 

508test operation_type_with_name(): 

509 document = parse("query MyQuery {\n" 

510 " packages {\n" 

511 " name\n" 

512 " }\n" 

513 "}") 

514 assert document.definitions.length() == 1 

515 operation_definition = (document 

516 .definitions[0] 

517 .executable_definition 

518 .operation_definition) 

519 assert operation_definition.operation_type == "query" 

520 assert operation_definition.name == "MyQuery" 

521 

522test variable(): 

523 document = parse("query ExampleQuery($name: String!) {\n" 

524 " standard_library {\n" 

525 " package(name: $name) {\n" 

526 " name\n" 

527 " }\n" 

528 " }\n" 

529 "}") 

530 assert document.definitions.length() == 1 

531 operation_definition = (document 

532 .definitions[0] 

533 .executable_definition 

534 .operation_definition) 

535 assert operation_definition.variable_definitions.length() == 1 

536 print(operation_definition.variable_definitions[0]) 

537 assert operation_definition.variable_definitions[0].name == "name" 

538 assert operation_definition.variable_definitions[0].value.name == "String" 

539 assert operation_definition.variable_definitions[0].value.non_null 

540 

541 document = parse("query ExampleQuery($count: Int) {\n" 

542 " standard_library {\n" 

543 " package(count: $count) {\n" 

544 " name\n" 

545 " }\n" 

546 " }\n" 

547 "}") 

548 assert document.definitions.length() == 1 

549 operation_definition = (document 

550 .definitions[0] 

551 .executable_definition 

552 .operation_definition) 

553 assert operation_definition.variable_definitions.length() == 1 

554 print(operation_definition.variable_definitions[0]) 

555 assert operation_definition.variable_definitions[0].name == "count" 

556 assert operation_definition.variable_definitions[0].value.name == "Int" 

557 assert not operation_definition.variable_definitions[0].value.non_null 

558 

559test parse_schema(): 

560 return 

561 parse("type Query {" 

562 " standard_library: StandardLibrary!" 

563 " statistics: Statistics!" 

564 "}" 

565 "type StandardLibrary {" 

566 " package(name: String!): Package!" 

567 " packages: [String!]" 

568 "}" 

569 "type Package {" 

570 " name: String!" 

571 " latest_release: Release!" 

572 " number_of_downloads: Int!" 

573 " builds: Boolean" 

574 "}" 

575 "type Release {" 

576 " version: String!" 

577 " description: String!" 

578 "}" 

579 "type Statistics {" 

580 " start_date_time: String!" 

581 " total_number_of_requests: Int!" 

582 " number_of_unique_visitors: Int!" 

583 " number_of_graphql_requests: Int!" 

584 " no_idle_client_handlers: Int!" 

585 "}")