1 module lexer;
2 
3 import std.experimental.logger;
4 import std.format : format;
5 import std.stdio;
6 
7 import tokenmodule;
8 
9 struct Lexer {
10 	string input;
11 	size_t stringPos;
12 
13 	size_t line;
14 	size_t column;
15 
16 	Token cur;
17 
18 	this(string input) @safe {
19 		this.input = input;
20 		this.stringPos = 0;
21 		this.line = 1;
22 		this.column = 1;
23 		this.buildToken();
24 	}
25 
26 	private bool isTokenStop() const @safe {
27 		return this.stringPos >= this.input.length 
28 			|| this.isTokenStop(this.input[this.stringPos]);
29 	}
30 
31 	private bool isTokenStop(const(char) c) const @safe {
32 		return 
33 			c == ' ' || c == '\t' || c == '\n' || c == ';' || c == '(' 
34 			|| c == ')' || c == '{' || c == '}' || c == '!' || c == '=' 
35 			|| c == '|' || c == '*' || c == '/' || c == '[' || c == ':'
36 			|| c == ']' || c == ',' || c == '@' || c == '#' || c == '$';
37 	}
38 
39 	private void eatComment() @safe {
40 		if(this.stringPos < this.input.length &&
41 				this.input[this.stringPos] == '#')
42 		{
43 			while(this.stringPos < this.input.length &&
44 				this.input[this.stringPos] != '\n')
45 			{
46 				++this.stringPos;	
47 			}
48 			++this.stringPos;
49 			++this.line;
50 		}
51 	}
52 
53 	private void eatWhitespace() @safe {
54 		import std.ascii : isWhite;
55 		while(this.stringPos < this.input.length) {
56 			this.eatComment();
57 			if(this.input[this.stringPos] == ' ') {
58 				++this.column;
59 			} else if(this.input[this.stringPos] == '\t') {
60 				++this.column;
61 			} else if(this.input[this.stringPos] == '\n') {
62 				this.column = 1;
63 				++this.line;
64 			} else {
65 				break;
66 			}
67 			++this.stringPos;
68 		}
69 	}
70 
71 	private void buildToken() @safe {
72 		import std.ascii : isAlphaNum;
73 		this.eatWhitespace();
74 
75 		if(this.stringPos >= this.input.length) {
76 			this.cur = Token(TokenType.undefined);
77 			return;
78 		}
79 
80 		if(this.input[this.stringPos] == ')') {
81 			this.cur = Token(TokenType.rparen, this.line, this.column);
82 			++this.column;
83 			++this.stringPos;
84 		} else if(this.input[this.stringPos] == '(') {
85 			this.cur = Token(TokenType.lparen, this.line, this.column);
86 			++this.column;
87 			++this.stringPos;
88 		} else if(this.input[this.stringPos] == ']') {
89 			this.cur = Token(TokenType.rbrack, this.line, this.column);
90 			++this.column;
91 			++this.stringPos;
92 		} else if(this.input[this.stringPos] == '[') {
93 			this.cur = Token(TokenType.lbrack, this.line, this.column);
94 			++this.column;
95 			++this.stringPos;
96 		} else if(this.input[this.stringPos] == '}') {
97 			this.cur = Token(TokenType.rcurly, this.line, this.column);
98 			++this.column;
99 			++this.stringPos;
100 		} else if(this.input[this.stringPos] == '$') {
101 			this.cur = Token(TokenType.dollar, this.line, this.column);
102 			++this.column;
103 			++this.stringPos;
104 		} else if(this.input[this.stringPos] == '!') {
105 			this.cur = Token(TokenType.exclamation, this.line, this.column);
106 			++this.column;
107 			++this.stringPos;
108 		} else if(this.input[this.stringPos] == '{') {
109 			this.cur = Token(TokenType.lcurly, this.line, this.column);
110 			++this.column;
111 			++this.stringPos;
112 		} else if(this.input[this.stringPos] == '|') {
113 			this.cur = Token(TokenType.pipe, this.line, this.column);
114 			++this.column;
115 			++this.stringPos;
116 		} else if(this.input[this.stringPos] == '@') {
117 			this.cur = Token(TokenType.at, this.line, this.column);
118 			++this.column;
119 			++this.stringPos;
120 		} else if(this.input[this.stringPos] == ',') {
121 			this.cur = Token(TokenType.comma, this.line, this.column);
122 			++this.column;
123 			++this.stringPos;
124 		} else if(this.input[this.stringPos] == '=') {
125 			this.cur = Token(TokenType.equal, this.line, this.column);
126 			++this.column;
127 			++this.stringPos;
128 		} else if(this.input[this.stringPos] == ':') {
129 			this.cur = Token(TokenType.colon, this.line, this.column);
130 			++this.column;
131 			++this.stringPos;
132 		} else {
133 			size_t b = this.stringPos;	
134 			size_t e = this.stringPos;
135 			switch(this.input[this.stringPos]) {
136 				case 'm':
137 					++this.stringPos;
138 					++this.column;
139 					++e;
140 					if(this.testCharAndInc('u', e)) {
141 						if(this.testCharAndInc('t', e)) {
142 							if(this.testCharAndInc('a', e)) {
143 								if(this.testCharAndInc('t', e)) {
144 									if(this.testCharAndInc('i', e)) {
145 										if(this.testCharAndInc('o', e)) {
146 											if(this.testCharAndInc('n', e)) {
147 												if(this.isTokenStop()) {
148 													this.cur =
149 														Token(TokenType.mutation,
150 																this.line,
151 																this.column);
152 													return;
153 												}
154 											}
155 										}
156 									}
157 								}
158 							}
159 						}
160 					}
161 					goto default;
162 				case '_':
163 					++this.stringPos;
164 					++this.column;
165 					++e;
166 					if(this.testCharAndInc('_', e)) {
167 						if(this.testCharAndInc('t', e)) {
168 							if(this.testCharAndInc('y', e)) {
169 								if(this.testCharAndInc('p', e)) {
170 									if(this.testCharAndInc('e', e)) {
171 										if(this.isTokenStop()) {
172 											this.cur = Token(TokenType.type,
173 													this.line, this.column);
174 											return;
175 										}
176 									}
177 								}
178 							}
179 						} else if(this.testCharAndInc('s', e)) {
180 							if(this.testCharAndInc('c', e)) {
181 								if(this.testCharAndInc('h', e)) {
182 									if(this.testCharAndInc('e', e)) {
183 										if(this.testCharAndInc('m', e)) {
184 											if(this.testCharAndInc('a', e)) {
185 												if(this.isTokenStop()) {
186 													this.cur =
187 														Token(TokenType.schema__,
188 																this.line,
189 																this.column);
190 													return;
191 												}
192 											}
193 										}
194 									}
195 								}
196 							}
197 						}
198 					}
199 					goto default;
200 				case 's':
201 					++this.stringPos;
202 					++this.column;
203 					++e;
204 					if(this.testCharAndInc('u', e)) {
205 						if(this.testCharAndInc('b', e)) {
206 							if(this.testCharAndInc('s', e)) {
207 								if(this.testCharAndInc('c', e)) {
208 									if(this.testCharAndInc('r', e)) {
209 										if(this.testCharAndInc('i', e)) {
210 											if(this.testCharAndInc('p', e)) {
211 												if(this.testCharAndInc('t', e)) {
212 													if(this.testCharAndInc('i', e)) {
213 														if(this.testCharAndInc('o', e)) {
214 															if(this.testCharAndInc('n', e)) {
215 																if(this.isTokenStop()) {
216 																	this.cur =
217 																		Token(TokenType.subscription,
218 																				this.line,
219 																				this.column);
220 																	return;
221 																}
222 															}
223 														}
224 													}
225 												}
226 											}
227 										}
228 									}
229 								}
230 							}
231 						}
232 					} else if(this.testCharAndInc('c', e)) {
233 						if(this.testCharAndInc('a', e)) {
234 							if(this.testCharAndInc('l', e)) {
235 								if(this.testCharAndInc('a', e)) {
236 									if(this.testCharAndInc('r', e)) {
237 										if(this.isTokenStop()) {
238 											this.cur = Token(TokenType.scalar, this.line, this.column);
239 											return;
240 										}
241 									}
242 								}
243 							}
244 						} else if(this.testCharAndInc('h', e)) {
245 							if(this.testCharAndInc('e', e)) {
246 								if(this.testCharAndInc('m', e)) {
247 									if(this.testCharAndInc('a', e)) {
248 										if(this.isTokenStop()) {
249 											this.cur = Token(TokenType.schema, this.line, this.column);
250 											return;
251 										}
252 									}
253 								}
254 							}
255 						}
256 					}
257 					goto default;
258 				case 'o':
259 					++this.stringPos;
260 					++this.column;
261 					++e;
262 					if(this.testCharAndInc('n', e)) {
263 						if(this.isTokenStop()) {
264 							this.cur = Token(TokenType.on_, this.line,
265 									this.column);
266 							return;
267 						}
268 					}
269 					goto default;
270 				case 'd':
271 					++this.stringPos;
272 					++this.column;
273 					++e;
274 					if(this.testCharAndInc('i', e)) {
275 						if(this.testCharAndInc('r', e)) {
276 							if(this.testCharAndInc('e', e)) {
277 								if(this.testCharAndInc('t', e)) {
278 									if(this.testCharAndInc('i', e)) {
279 										if(this.testCharAndInc('v', e)) {
280 											if(this.testCharAndInc('e', e)) {
281 												if(this.isTokenStop()) {
282 													this.cur = Token(TokenType.directive,
283 															this.line, this.column);
284 													return;
285 												}
286 											}
287 										}
288 									}
289 								}
290 							}
291 						}
292 					}
293 					goto default;
294 				case 'e':
295 					++this.stringPos;
296 					++this.column;
297 					++e;
298 					if(this.testCharAndInc('n', e)) {
299 						if(this.testCharAndInc('u', e)) {
300 							if(this.testCharAndInc('m', e)) {
301 								if(this.isTokenStop()) {
302 									this.cur = Token(TokenType.enum_,
303 											this.line, this.column);
304 									return;
305 								}
306 							}
307 						}
308 					} else if(this.testCharAndInc('x', e)) {
309 						if(this.testCharAndInc('t', e)) {
310 							if(this.testCharAndInc('e', e)) {
311 								if(this.testCharAndInc('n', e)) {
312 									if(this.testCharAndInc('d', e)) {
313 										if(this.isTokenStop()) {
314 											this.cur = Token(TokenType.extend,
315 													this.line, this.column);
316 											return;
317 										}
318 									}
319 								}
320 							}
321 						}
322 					}
323 					goto default;
324 				case 'i':
325 					++this.stringPos;
326 					++this.column;
327 					++e;
328 					if(this.testCharAndInc('n', e)) {
329 						if(this.testCharAndInc('p', e)) {
330 							if(this.testCharAndInc('u', e)) {
331 								if(this.testCharAndInc('t', e)) {
332 									if(this.isTokenStop()) {
333 										this.cur = Token(TokenType.input,
334 												this.line, this.column);
335 										return;
336 									}
337 								}
338 							}
339 						} else if(this.testCharAndInc('t', e)) {
340 							if(this.testCharAndInc('e', e)) {
341 								if(this.testCharAndInc('r', e)) {
342 									if(this.testCharAndInc('f', e)) {
343 										if(this.testCharAndInc('a', e)) {
344 											if(this.testCharAndInc('c', e)) {
345 												if(this.testCharAndInc('e', e)) {
346 													if(this.isTokenStop()) {
347 														this.cur = Token(TokenType.interface_,
348 																this.line, this.column);
349 														return;
350 													}
351 												}
352 											}
353 										}
354 									}
355 								}
356 							}
357 						}
358 					} else if(this.testCharAndInc('m', e)) {
359 						if(this.testCharAndInc('p', e)) {
360 							if(this.testCharAndInc('l', e)) {
361 								if(this.testCharAndInc('e', e)) {
362 									if(this.testCharAndInc('m', e)) {
363 										if(this.testCharAndInc('e', e)) {
364 											if(this.testCharAndInc('n', e)) {
365 												if(this.testCharAndInc('t', e)) {
366 													if(this.testCharAndInc('s', e)) {
367 														if(this.isTokenStop()) {
368 															this.cur = Token(TokenType.implements,
369 																	this.line, this.column);
370 															return;
371 														}
372 													}
373 												}
374 											}
375 										}
376 									}
377 								}
378 							}
379 						}
380 					}
381 					
382 					goto default;
383 				case 'f':
384 					++this.stringPos;
385 					++this.column;
386 					++e;
387 					if(this.testCharAndInc('a', e)) {
388 						if(this.testCharAndInc('l', e)) {
389 							if(this.testCharAndInc('s', e)) {
390 								if(this.testCharAndInc('e', e)) {
391 									if(this.isTokenStop()) {
392 										this.cur = Token(TokenType.false_,
393 												this.line, this.column);
394 										return;
395 									}
396 								}
397 							}
398 						}
399 					} else if(this.testCharAndInc('r', e)) {
400 						if(this.testCharAndInc('a', e)) {
401 							if(this.testCharAndInc('g', e)) {
402 								if(this.testCharAndInc('m', e)) {
403 									if(this.testCharAndInc('e', e)) {
404 										if(this.testCharAndInc('n', e)) {
405 											if(this.testCharAndInc('t', e)) {
406 												if(this.isTokenStop()) {
407 													this.cur =
408 														Token(TokenType.fragment,
409 																this.line,
410 																this.column);
411 													return;
412 												}
413 											}
414 										}
415 									}
416 								}
417 							}
418 						}
419 					}
420 					goto default;
421 				/*case '@':
422 					++this.stringPos;
423 					++this.column;
424 					++e;
425 					if(isTokenStop()) {
426 						this.cur = Token(TokenType.at, this.line, this.column);
427 						return;
428 					}
429 					goto default;*/
430 				case 'q':
431 					++this.stringPos;
432 					++this.column;
433 					++e;
434 					if(this.testCharAndInc('u', e)) {
435 						if(this.testCharAndInc('e', e)) {
436 							if(this.testCharAndInc('r', e)) {
437 								if(this.testCharAndInc('y', e)) {
438 									if(this.isTokenStop()) {
439 										this.cur = Token(TokenType.query,
440 												this.line, this.column);
441 										return;
442 									}
443 								}
444 							}
445 						}
446 					}
447 					goto default;
448 				case 't':
449 					++this.stringPos;
450 					++this.column;
451 					++e;
452 					if(this.testCharAndInc('r', e)) {
453 						if(this.testCharAndInc('u', e)) {
454 							if(this.testCharAndInc('e', e)) {
455 								if(this.isTokenStop()) {
456 									this.cur = Token(TokenType.true_,
457 											this.line, this.column);
458 									return;
459 								}
460 							}
461 						}
462 					} else if(this.testCharAndInc('y', e)) {
463 						if(this.testCharAndInc('p', e)) {
464 							if(this.testCharAndInc('e', e)) {
465 								if(this.isTokenStop()) {
466 									this.cur = Token(TokenType.type,
467 											this.line, this.column);
468 									return;
469 								}
470 							}
471 						}
472 					}
473 					goto default;
474 				case 'n':
475 					++this.stringPos;
476 					++this.column;
477 					++e;
478 					if(this.testCharAndInc('u', e)) {
479 						if(this.testCharAndInc('l', e)) {
480 							if(this.testCharAndInc('l', e)) {
481 								if(this.isTokenStop()) {
482 									this.cur = Token(TokenType.null_,
483 											this.line, this.column);
484 									return;
485 								}
486 							}
487 						}
488 					}
489 					goto default;
490 				case 'u':
491 					++this.stringPos;
492 					++this.column;
493 					++e;
494 					if(this.testCharAndInc('n', e)) {
495 						if(this.testCharAndInc('i', e)) {
496 							if(this.testCharAndInc('o', e)) {
497 								if(this.testCharAndInc('n', e)) {
498 									if(this.isTokenStop()) {
499 										this.cur = Token(TokenType.union_,
500 												this.line, this.column);
501 										return;
502 									}
503 								}
504 							}
505 						}
506 					}
507 					goto default;
508 				case '.':
509 					++this.stringPos;
510 					++this.column;
511 					++e;
512 					if(this.testCharAndInc('.', e)) {
513 						if(this.testCharAndInc('.', e)) {
514 							//if(this.stringPos < this.input.length 
515 							//	&& isAlphaNum(this.input[this.stringPos])) 
516 							if(this.isTokenStop() 
517 									|| (this.stringPos < this.input.length 
518 										&& isAlphaNum(this.input[this.stringPos])
519 										)
520 								) 
521 							{
522 								this.cur = Token(TokenType.dots, this.line,
523 										this.column);
524 								return;
525 							}
526 						}
527 					}
528 					throw new Exception(format(
529 							"failed to parse \"...\" at line %s column %s",
530 							this.line, this.column
531 						));
532 				case '-':
533 					++this.stringPos;
534 					++this.column;
535 					++e;
536 					goto case '0';
537 				case '+':
538 					++this.stringPos;
539 					++this.column;
540 					++e;
541 					goto case '0';
542 				case '0': .. case '9':
543 					do {
544 						++this.stringPos;
545 						++this.column;
546 						++e;
547 					} while(this.stringPos < this.input.length 
548 							&& this.input[this.stringPos] >= '0'
549 							&& this.input[this.stringPos] <= '9');
550 					
551 					if(this.stringPos >= this.input.length
552 							|| this.input[this.stringPos] != '.') 
553 					{
554 						this.cur = Token(TokenType.intValue, this.input[b ..
555 								e], this.line, this.column);
556 						return;
557 					} else if(this.stringPos < this.input.length
558 							&& this.input[this.stringPos] == '.')
559 					{
560 						do {
561 							++this.stringPos;
562 							++this.column;
563 							++e;
564 						} while(this.stringPos < this.input.length 
565 								&& this.input[this.stringPos] >= '0'
566 								&& this.input[this.stringPos] <= '9');
567 
568 						this.cur = Token(TokenType.floatValue, this.input[b ..
569 								e], this.line, this.column);
570 						return;
571 					}
572 					goto default;
573 				case '"':
574 					++this.stringPos;
575 					++this.column;
576 					++e;
577 					while(this.stringPos < this.input.length
578 							&& (this.input[this.stringPos] != '"'
579 								|| (this.input[this.stringPos] == '"' 
580 									&& this.input[this.stringPos - 1U] == '\\')
581 						 		)
582 						)
583 					{
584 						++this.stringPos;
585 						++this.column;
586 						++e;
587 					}
588 					++this.stringPos;
589 					++this.column;
590 					this.cur = Token(TokenType.stringValue, this.input[b + 1
591 							.. e], this.line, this.column);
592 					break;
593 				default:
594 					while(!this.isTokenStop()) {
595 						//writefln("455 '%s'", this.input[this.stringPos]);
596 						++this.stringPos;
597 						++this.column;
598 						++e;
599 					}
600 					//writefln("%s %s %s '%s'", b, e, this.stringPos, this.input[b .. e]);
601 					//do {
602 					//	writefln("'%s'", this.input[this.stringPos]);
603 					//	++this.stringPos;
604 					//	++this.column;
605 					//	++e;
606 					//} while(!this.isTokenStop());
607 					//writefln("%s %s", TokenType.name, this.input[b .. e]);
608 					this.cur = Token(TokenType.name, this.input[b .. e],
609 							this.line, this.column
610 						);
611 					break;
612 			}
613 		}
614 	}
615 
616 	bool testCharAndInc(const(char) c, ref size_t e) @safe {
617 		if(this.stringPos < this.input.length 
618 				&& this.input[this.stringPos] == c)
619 		{
620 			++this.column;
621 			++this.stringPos;
622 			++e;
623 			return true;
624 		} else {
625 			return false;
626 		}
627 	}
628 
629 	@property bool empty() const @safe {
630 		return this.stringPos >= this.input.length
631 			&& this.cur.type == TokenType.undefined;
632 	}
633 
634 	Token front() @property @safe {
635 		return this.cur;
636 	}
637 
638 	@property Token front() const @safe @nogc pure {
639 		return this.cur;
640 	}
641 
642 	void popFront() @safe {
643 		this.buildToken();		
644 	}
645 }
646 
647 unittest {
648 	string f = "f ";
649 	auto l = Lexer(f);
650 	assert(!l.empty);
651 	assert(l.front.type == TokenType.name);
652 	assert(l.front.value == "f", format("'%s'", l.front.value));
653 }
654 
655 unittest {
656 	string f = "... ";
657 
658 	auto l = Lexer(f);
659 	assert(!l.empty);
660 	assert(l.front.type == TokenType.dots);
661 	l.popFront();
662 	assert(l.empty);
663 }
664 
665 unittest {
666 	string f = "name! ";
667 	auto l = Lexer(f);
668 	assert(!l.empty);
669 	assert(l.front.type == TokenType.name);
670 	assert(l.front.value == "name", format("'%s'", l.front.value));
671 	l.popFront();
672 	assert(!l.empty);
673 	assert(l.front.type == TokenType.exclamation);
674 	l.popFront();
675 	assert(l.empty);
676 }
677 
678 unittest {
679 	string f = "fragment";
680 	auto l = Lexer(f);
681 	assert(!l.empty);
682 	assert(l.front.type == TokenType.fragment);
683 }
684 
685 unittest {
686 	string f = `
687 		mutation {
688 		  likeStory(storyID: 12345) {
689 		    story {
690 		      likeCount
691 		    }
692 		  }
693 		}`;
694 	auto l = Lexer(f);
695 	assert(!l.empty);
696 	assert(l.front.type == TokenType.mutation);
697 	l.popFront();
698 	assert(!l.empty);
699 	assert(l.front.type == TokenType.lcurly);
700 	l.popFront();
701 	assert(!l.empty);
702 	assert(l.front.type == TokenType.name);
703 	l.popFront();
704 	assert(!l.empty);
705 	assert(l.front.type == TokenType.lparen);
706 	l.popFront();
707 	assert(!l.empty);
708 	assert(l.front.type == TokenType.name);
709 	l.popFront();
710 	assert(!l.empty);
711 	assert(l.front.type == TokenType.colon, format("%s", l.front.type));
712 	l.popFront();
713 	assert(!l.empty);
714 	assert(l.front.type == TokenType.intValue);
715 	l.popFront();
716 	assert(!l.empty);
717 	assert(l.front.type == TokenType.rparen);
718 	l.popFront();
719 	assert(!l.empty);
720 	assert(l.front.type == TokenType.lcurly);
721 	l.popFront();
722 	assert(!l.empty);
723 	assert(l.front.type == TokenType.name);
724 	l.popFront();
725 	assert(!l.empty);
726 	assert(l.front.type == TokenType.lcurly);
727 	l.popFront();
728 	assert(!l.empty);
729 	assert(l.front.type == TokenType.name);
730 	l.popFront();
731 	assert(!l.empty);
732 	assert(l.front.type == TokenType.rcurly);
733 	l.popFront();
734 	assert(!l.empty);
735 	assert(l.front.type == TokenType.rcurly);
736 	l.popFront();
737 	assert(!l.empty);
738 	assert(l.front.type == TokenType.rcurly);
739 	l.popFront();
740 	assert(l.empty);
741 }
742 
743 unittest {
744 	string f = `
745 		query withFragments {
746 		  user(id: +4) {
747 			# super cool comment
748 friends(first: -10.3) {
749 		      ...friendFields
750 			  null false true
751 		    }
752 		    mutualFriends(first: 10) {
753 		      ...friendFields
754 		    }
755 		  }
756 		}
757 		
758 		fragment friendFields on User {
759 		  id
760 		  name
761 		  profilePic(size: 50)
762 		}`;
763 	auto l = Lexer(f);
764 	assert(!l.empty);
765 	assert(l.front.type == TokenType.query);
766 	l.popFront();
767 	assert(!l.empty);
768 	assert(l.front.type == TokenType.name);
769 	assert(l.front.value == "withFragments");
770 	l.popFront();
771 	l.popFront();
772 	assert(!l.empty);
773 	assert(l.front.type == TokenType.name);
774 	assert(l.front.value == "user");
775 	l.popFront();
776 	l.popFront();
777 	assert(!l.empty);
778 	assert(l.front.type == TokenType.name);
779 	assert(l.front.value == "id", l.front.value);
780 	l.popFront();
781 	assert(!l.empty);
782 	assert(l.front.type == TokenType.colon);
783 	l.popFront();
784 	assert(!l.empty);
785 	assert(l.front.type == TokenType.intValue);
786 	assert(l.front.value == "+4");
787 	l.popFront();
788 	assert(!l.empty);
789 	assert(l.front.type == TokenType.rparen);
790 	l.popFront();
791 	l.popFront();
792 	assert(!l.empty);
793 	assert(l.front.type == TokenType.name);
794 	assert(l.front.value == "friends");
795 	l.popFront();
796 	assert(!l.empty);
797 	assert(l.front.type == TokenType.lparen);
798 	l.popFront();
799 	assert(!l.empty);
800 	assert(l.front.type == TokenType.name);
801 	assert(l.front.value == "first");
802 	l.popFront();
803 	l.popFront();
804 	assert(!l.empty);
805 	assert(l.front.type == TokenType.floatValue, format("%s", l.front.type));
806 	assert(l.front.value == "-10.3", l.front.value);
807 	l.popFront();
808 	l.popFront();
809 	l.popFront();
810 	assert(!l.empty);
811 	assert(l.front.type == TokenType.dots, format("%s", l.front.type));
812 	l.popFront();
813 	assert(!l.empty);
814 	assert(l.front.type == TokenType.name, format("%s", l.front.type));
815 	assert(l.front.value == "friendFields");
816 	l.popFront();
817 	assert(!l.empty);
818 	assert(l.front.type == TokenType.null_, format("%s", l.front.type));
819 	l.popFront();
820 	assert(!l.empty);
821 	assert(l.front.type == TokenType.false_, format("%s", l.front.type));
822 	l.popFront();
823 	assert(!l.empty);
824 	assert(l.front.type == TokenType.true_, format("%s", l.front.type));
825 	while(!l.empty) {
826 		l.popFront();
827 	}
828 }
829 
830 unittest {
831 	string f = `
832 		query withFragments {
833 		  user(id: "hello") {
834 		  }
835 		}`;
836 
837 	auto l = Lexer(f);
838 	assert(!l.empty);
839 	assert(l.front.type == TokenType.query);
840 	l.popFront();
841 	assert(!l.empty);
842 	assert(l.front.type == TokenType.name);
843 	assert(l.front.value == "withFragments");
844 	l.popFront();
845 	l.popFront();
846 	assert(!l.empty);
847 	assert(l.front.type == TokenType.name);
848 	assert(l.front.value == "user");
849 	l.popFront();
850 	l.popFront();
851 	assert(!l.empty);
852 	assert(l.front.type == TokenType.name);
853 	assert(l.front.value == "id", l.front.value);
854 	l.popFront();
855 	assert(!l.empty);
856 	assert(l.front.type == TokenType.colon);
857 	l.popFront();
858 	assert(!l.empty);
859 	assert(l.front.type == TokenType.stringValue);
860 	assert(l.front.value == "hello", format("'%s' '%s'", l.front.value, "hello"));
861 	l.popFront();
862 	assert(!l.empty);
863 	assert(l.front.type == TokenType.rparen);
864 }