tinygql 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 114da94b6827645fb92f0f3e85b9e4f34b64aaee8449c143217cc75fc903ab3c
4
- data.tar.gz: 27031f9423d30f0453a6737be1184cb8c724a6290bc432b7e35dea255fa549a5
3
+ metadata.gz: 4a68b5713ff3ff9e39df6e5eb89f7eec5fc74f524df33f9840f58cd8ac5027ec
4
+ data.tar.gz: f823e82df55af3ca0327889627e99f6d896242327369b698d505b921bba819a9
5
5
  SHA512:
6
- metadata.gz: a4362f8d3c3d638c715c4686ec9714d59f59d411be390cf115d0b7e8bc4f710fcbfe935ca058092f24935964fd29905643e6f1f4edeb84e238e1af0d5d2eff4e
7
- data.tar.gz: 5ec4f82ff306221b2c8e008b3bb76feb1c55917ab26334e1531f7eb8e58f03e44a29901fe92765585cfa195c41a107fdd13337b3bb844f13bb9985f1394f8ee0
6
+ metadata.gz: a0b4cd65e5c017f820e7198479a54dde8f676eb52fac56d9672a173ef282b21862a028f4a685d59c49c6dbfbd2d213148eff27674b071cff561e1cd6fe685251
7
+ data.tar.gz: 266db1a7ad62bae36b375403286ec1a08b774f2fad5dc1808bacbac5e86e89ea165d9dc9d1a7598e9e4d059a7e55e0a88396df786f17aba2c6c011534a201e14
@@ -10,7 +10,7 @@ jobs:
10
10
  fail-fast: false
11
11
  matrix:
12
12
  os: [ubuntu, macos]
13
- ruby: [ head, 3.2 ]
13
+ ruby: [ head, 3.2, truffleruby, truffleruby-head, jruby ]
14
14
 
15
15
  steps:
16
16
  - uses: actions/checkout@v3
@@ -31,7 +31,7 @@ jobs:
31
31
  fail-fast: false
32
32
  matrix:
33
33
  os: [ubuntu, macos]
34
- ruby: [ head, 3.2 ]
34
+ ruby: [ head, 3.2, truffleruby, truffleruby-head ]
35
35
 
36
36
  steps:
37
37
  - uses: actions/checkout@v3
data/Gemfile CHANGED
@@ -1,6 +1,5 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gem "benchmark-ips"
4
- gem "vernier"
5
4
 
6
5
  gemspec
data/README.md CHANGED
@@ -66,6 +66,29 @@ ast = TinyGQL.parse "{ neat { cool } }"
66
66
  p ast.fold(Fold, []) # => ["neat", "cool"]
67
67
  ```
68
68
 
69
+ Nodes store their position in the source GraphQL document.
70
+ If you'd like to extract the line number of the node, you'll need to keep a reference to the document and pass it to the `line` method on the node:
71
+
72
+ ```ruby
73
+ doc = <<-eod
74
+ mutation {
75
+ likeStory(sturyID: 12345) {
76
+ story {
77
+ likeCount
78
+ }
79
+ }
80
+ }
81
+
82
+ eod
83
+
84
+ parser = TinyGQL::Parser.new doc
85
+ ast = parser.parse
86
+
87
+ ast.find_all(&:field?).each { |node|
88
+ p node.name => node.line(doc)
89
+ }
90
+ ```
91
+
69
92
  ## LICENSE:
70
93
 
71
94
  I've licensed this code as Apache 2.0, but the lexer is from [GraphQL-Ruby](https://github.com/rmosolgo/graphql-ruby/blob/772734dfcc7aa0513c867259912474ef0ba799c3/lib/graphql/language/lexer.rb) and is under the MIT license.
data/bin/make_hash.rb ADDED
@@ -0,0 +1,51 @@
1
+ require "tinygql"
2
+
3
+ # Calculate a perfect hash for GraphQL keywords
4
+
5
+ def bits x
6
+ count = 0
7
+ while x > 0
8
+ count += 1
9
+ x >>= 1
10
+ end
11
+ count
12
+ end
13
+
14
+ # on is too short, and subscription is the longest.
15
+ # The lexer can easily detect them by length, so lets calculate a perfect
16
+ # hash for the rest.
17
+ kws = TinyGQL::Lexer::KEYWORDS - ["on", "subscription"]
18
+ MASK = (1 << bits(kws.length)) - 1
19
+
20
+ prefixes = kws.map { |word| word[1, 2] }
21
+
22
+ # make sure they're unique
23
+ raise "Not unique" unless prefixes.uniq == prefixes
24
+
25
+ keys = prefixes.map { |prefix|
26
+ prefix.bytes.reverse.inject(0) { |c, byte|
27
+ c << 8 | byte
28
+ }
29
+ }
30
+
31
+ shift = 32 - bits(kws.length) # use the top bits
32
+
33
+ c = 13
34
+ loop do
35
+ z = keys.map { |k| ((k * c) >> shift) & MASK }
36
+ break if z.uniq.length == z.length
37
+ c += 1
38
+ end
39
+
40
+ table = kws.zip(keys).each_with_object([]) { |(word, k), o|
41
+ hash = ((k * c) >> shift) & MASK
42
+ o[hash] = word.upcase.to_sym
43
+ }
44
+
45
+ print "KW_LUT ="
46
+ pp table
47
+ puts <<-eomethod
48
+ def hash key
49
+ (key * #{c}) >> #{shift} & #{sprintf("%#0x", MASK)}
50
+ end
51
+ eomethod
data/lib/tinygql/lexer.rb CHANGED
@@ -71,11 +71,36 @@ module TinyGQL
71
71
  ESCAPED_QUOTE = /\\"/;
72
72
  STRING_CHAR = /#{ESCAPED_QUOTE}|[^"\\]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/
73
73
 
74
- LIT_NAME_LUT = Literals.constants.each_with_object([]) { |n, o|
74
+ PUNCT_LUT = Literals.constants.each_with_object([]) { |n, o|
75
75
  o[Literals.const_get(n).ord] = n
76
76
  }
77
77
 
78
- LIT = Regexp.union(Literals.constants.map { |n| Literals.const_get(n) })
78
+ LEAD_BYTES = Array.new(255) { 0 }
79
+
80
+ module LeadBytes
81
+ INT = 1
82
+ KW = 2
83
+ ELLIPSIS = 3
84
+ STRING = 4
85
+ PUNCT = 5
86
+ IDENT = 6
87
+ end
88
+
89
+ 10.times { |i| LEAD_BYTES[i.to_s.ord] = LeadBytes::INT }
90
+
91
+ ("A".."Z").each { |chr| LEAD_BYTES[chr.ord] = LeadBytes::IDENT }
92
+ ("a".."z").each { |chr| LEAD_BYTES[chr.ord] = LeadBytes::IDENT }
93
+ LEAD_BYTES['_'.ord] = LeadBytes::IDENT
94
+
95
+ KEYWORDS.each { |kw| LEAD_BYTES[kw.getbyte(0)] = LeadBytes::KW }
96
+
97
+ LEAD_BYTES['.'.ord] = LeadBytes::ELLIPSIS
98
+
99
+ LEAD_BYTES['"'.ord] = LeadBytes::STRING
100
+
101
+ Literals.constants.each_with_object([]) { |n, o|
102
+ LEAD_BYTES[Literals.const_get(n).ord] = LeadBytes::PUNCT
103
+ }
79
104
 
80
105
  QUOTED_STRING = %r{#{QUOTE} ((?:#{STRING_CHAR})*) #{QUOTE}}x
81
106
  BLOCK_STRING = %r{
@@ -95,11 +120,10 @@ module TinyGQL
95
120
 
96
121
  @string = string
97
122
  @scan = StringScanner.new string
123
+ @start = nil
98
124
  end
99
125
 
100
- def pos
101
- @scan.pos
102
- end
126
+ attr_reader :start
103
127
 
104
128
  def line
105
129
  @scan.string[0, @scan.pos].count("\n") + 1
@@ -109,66 +133,57 @@ module TinyGQL
109
133
  @scan.eos?
110
134
  end
111
135
 
112
- KW_LUT = [:FRAGMENT,
113
- :INTERFACE,
114
- :MUTATION,
115
- :EXTEND,
116
- :FALSE,
117
- :ENUM,
118
- :TRUE,
119
- :NULL,
120
- nil,
121
- nil,
122
- nil,
123
- nil,
124
- nil,
125
- nil,
126
- :QUERY,
127
- nil,
128
- nil,
129
- nil,
130
- :REPEATABLE,
131
- :IMPLEMENTS,
132
- :INPUT,
133
- :TYPE,
134
- :SCHEMA,
135
- nil,
136
- nil,
137
- nil,
138
- :DIRECTIVE,
139
- :UNION,
140
- nil,
141
- nil,
142
- :SCALAR]
143
-
144
136
  def advance
145
137
  @scan.skip(IGNORE)
146
138
 
147
- case
148
- when @scan.eos? then false
149
- when @scan.skip(ELLIPSIS) then :ELLIPSIS
150
- when tok = LIT_NAME_LUT[@string.getbyte(@scan.pos)] then
139
+ return false if @scan.eos?
140
+
141
+ @start = @scan.pos
142
+
143
+ lead_byte = @string.getbyte(@start)
144
+ lead_code = LEAD_BYTES[lead_byte]
145
+
146
+ if lead_code == LeadBytes::PUNCT
151
147
  @scan.pos += 1
152
- tok
153
- when @scan.skip(KW_RE) then
154
- len = @scan.matched_size
155
- return :ON if len == 2
156
- return :SUBSCRIPTION if len == 12
157
-
158
- pos = @scan.pos - len
159
-
160
- # First 3 bytes are unique, so we'll hash on those
161
- key = (@string.getbyte(pos + 2) << 16) |
162
- (@string.getbyte(pos + 1) << 8) |
163
- @string.getbyte(pos + 0)
164
-
165
- KW_LUT[hash(key)]
166
- when @scan.skip(IDENTIFIER) then :IDENTIFIER
167
- when @scan.skip(BLOCK_STRING) then :STRING
168
- when @scan.skip(QUOTED_STRING) then :STRING
169
- when @scan.skip(NUMERIC) then (@scan[1] ? :FLOAT : :INT)
148
+ PUNCT_LUT[lead_byte]
149
+
150
+ elsif lead_code == LeadBytes::KW
151
+ if len = @scan.skip(KW_RE)
152
+ return :ON if len == 2
153
+ return :SUBSCRIPTION if len == 12
154
+
155
+ pos = @start
156
+
157
+ # Second 2 bytes are unique, so we'll hash on those
158
+ key = (@string.getbyte(pos + 2) << 8) | @string.getbyte(pos + 1)
159
+
160
+ KW_LUT[_hash(key)]
161
+ else
162
+ @scan.skip(IDENTIFIER)
163
+ :IDENTIFIER
164
+ end
165
+
166
+ elsif lead_code == LeadBytes::IDENT
167
+ @scan.skip(IDENTIFIER)
168
+ :IDENTIFIER
169
+
170
+ elsif lead_code == LeadBytes::INT
171
+ @scan.skip(NUMERIC)
172
+ @scan[1] ? :FLOAT : :INT
173
+
174
+ elsif lead_code == LeadBytes::ELLIPSIS
175
+ 2.times do |i|
176
+ raise unless @string.getbyte(@start + i + 1) == 46
177
+ end
178
+ @scan.pos += 3
179
+ :ELLIPSIS
180
+
181
+ elsif lead_code == LeadBytes::STRING
182
+ raise unless @scan.skip(BLOCK_STRING) || @scan.skip(QUOTED_STRING)
183
+ :STRING
184
+
170
185
  else
171
- @scan.getch
186
+ @scan.pos += 1
172
187
  :UNKNOWN_CHAR
173
188
  end
174
189
  end
@@ -193,6 +208,8 @@ module TinyGQL
193
208
  return unless tok = advance
194
209
  val = case tok
195
210
  when :STRING then string_value
211
+ when :ELLIPSIS then
212
+ @string.byteslice(@scan.pos - 3, 3)
196
213
  when *Literals.constants
197
214
  @string.byteslice(@scan.pos - 1, 1)
198
215
  else
@@ -315,10 +332,41 @@ module TinyGQL
315
332
  lines.size > 1 ? lines.join("\n") : (lines.first || "".dup)
316
333
  end
317
334
 
318
- def hash key
319
- # Constant came from perfect hash generation
320
- m = key * 145291
321
- (m >> 28) & 0x1f
335
+ KW_LUT =[:INTERFACE,
336
+ :MUTATION,
337
+ :EXTEND,
338
+ :FALSE,
339
+ :ENUM,
340
+ :TRUE,
341
+ :NULL,
342
+ nil,
343
+ nil,
344
+ nil,
345
+ nil,
346
+ nil,
347
+ nil,
348
+ nil,
349
+ :QUERY,
350
+ nil,
351
+ nil,
352
+ :REPEATABLE,
353
+ :IMPLEMENTS,
354
+ :INPUT,
355
+ :TYPE,
356
+ :SCHEMA,
357
+ nil,
358
+ nil,
359
+ nil,
360
+ :DIRECTIVE,
361
+ :UNION,
362
+ nil,
363
+ nil,
364
+ :SCALAR,
365
+ nil,
366
+ :FRAGMENT]
367
+
368
+ def _hash key
369
+ (key * 18592990) >> 27 & 0x1f
322
370
  end
323
371
  end
324
372
  end
data/lib/tinygql/nodes.rb CHANGED
@@ -3,16 +3,16 @@ module TinyGQL
3
3
  class Node
4
4
  include Enumerable
5
5
 
6
- # `pos` is the position of the _end_ of a token
7
- attr_reader :pos
6
+ # `start` is the start position of this node in the source document
7
+ attr_reader :start
8
8
 
9
- def initialize pos
10
- @pos = pos
9
+ def initialize start
10
+ @start = start
11
11
  end
12
12
 
13
13
  # Return the line of this node given `doc`
14
14
  def line doc
15
- doc[0, @pos].count("\n") + 1
15
+ doc[0, @start].count("\n") + 1
16
16
  end
17
17
 
18
18
  def document?; false; end
@@ -3,16 +3,16 @@ module TinyGQL
3
3
  class Node
4
4
  include Enumerable
5
5
 
6
- # `pos` is the position of the _end_ of a token
7
- attr_reader :pos
6
+ # `start` is the start position of this node in the source document
7
+ attr_reader :start
8
8
 
9
- def initialize pos
10
- @pos = pos
9
+ def initialize start
10
+ @start = start
11
11
  end
12
12
 
13
13
  # Return the line of this node given `doc`
14
14
  def line doc
15
- doc[0, @pos].count("\n") + 1
15
+ doc[0, @start].count("\n") + 1
16
16
  end
17
17
 
18
18
  <%- nodes.each do |node| -%>
@@ -13,7 +13,7 @@ module TinyGQL
13
13
 
14
14
  def initialize doc
15
15
  @lexer = Lexer.new doc
16
- @token_name = @lexer.advance
16
+ accept_token
17
17
  end
18
18
 
19
19
  def parse
@@ -25,7 +25,7 @@ module TinyGQL
25
25
  attr_reader :token_name
26
26
 
27
27
  def pos
28
- @lexer.pos
28
+ @lexer.start
29
29
  end
30
30
 
31
31
  def document
@@ -477,7 +477,17 @@ module TinyGQL
477
477
  end
478
478
 
479
479
  def operation_type
480
- expect_tokens([:QUERY, :MUTATION, :SUBSCRIPTION])
480
+ val = if at?(:QUERY)
481
+ "query"
482
+ elsif at?(:MUTATION)
483
+ "mutation"
484
+ elsif at?(:SUBSCRIPTION)
485
+ "subscription"
486
+ else
487
+ expect_token(:QUERY)
488
+ end
489
+ accept_token
490
+ val
481
491
  end
482
492
 
483
493
  def directives
@@ -528,7 +538,7 @@ module TinyGQL
528
538
 
529
539
  def variable_definition
530
540
  loc = pos
531
- var = variable
541
+ var = variable if at?(:VAR_SIGN)
532
542
  expect_token(:COLON)
533
543
  type = self.type
534
544
  default_value = if at?(:EQUALS)
@@ -608,7 +618,7 @@ module TinyGQL
608
618
  accept_token
609
619
  Nodes::BooleanValue.new(pos, "false")
610
620
  else
611
- expect_tokens([:TRUE, :FALSE])
621
+ expect_token(:TRUE)
612
622
  end
613
623
  end
614
624
 
@@ -643,15 +653,14 @@ module TinyGQL
643
653
  end
644
654
 
645
655
  def variable
646
- return unless at?(:VAR_SIGN)
647
656
  loc = pos
648
- accept_token
657
+ expect_token(:VAR_SIGN)
649
658
  Nodes::Variable.new loc, name
650
659
  end
651
660
 
652
661
  def name
653
662
  case token_name
654
- when :IDENTIFIER then accept_token_value
663
+ when :IDENTIFIER then expect_token_value(:IDENTIFIER)
655
664
  when :TYPE then
656
665
  accept_token
657
666
  "type"
@@ -662,7 +671,7 @@ module TinyGQL
662
671
  accept_token
663
672
  "input"
664
673
  else
665
- expect_token_value(:IDENTIFIER)
674
+ expect_token(:IDENTIFIER)
666
675
  end
667
676
  end
668
677
 
@@ -670,13 +679,6 @@ module TinyGQL
670
679
  @token_name = @lexer.advance
671
680
  end
672
681
 
673
- # Only use when we care about the accepted token's value
674
- def accept_token_value
675
- token_value = @lexer.token_value
676
- accept_token
677
- token_value
678
- end
679
-
680
682
  def expect_token tok
681
683
  unless at?(tok)
682
684
  raise UnexpectedToken, "Expected token #{tok}, actual: #{token_name} #{@lexer.token_value} line: #{@lexer.line}"
@@ -697,15 +699,6 @@ module TinyGQL
697
699
  token_value
698
700
  end
699
701
 
700
- def expect_tokens toks
701
- token_value = @lexer.token_value
702
- unless toks.any? { |tok| at?(tok) }
703
- raise UnexpectedToken, "Expected token #{tok}, actual: #{token_name}"
704
- end
705
- accept_token
706
- token_value
707
- end
708
-
709
702
  def at? tok
710
703
  token_name == tok
711
704
  end
@@ -1,3 +1,3 @@
1
1
  module TinyGQL
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.1'
3
3
  end
@@ -461,5 +461,277 @@ module TinyGQL
461
461
  end
462
462
 
463
463
  end
464
+
465
+ module Null
466
+
467
+ def handle_document obj
468
+ end
469
+
470
+ def handle_operation_definition obj
471
+ end
472
+
473
+ def handle_variable obj
474
+ end
475
+
476
+ def handle_named_type obj
477
+ end
478
+
479
+ def handle_not_null_type obj
480
+ end
481
+
482
+ def handle_list_type obj
483
+ end
484
+
485
+ def handle_variable_definition obj
486
+ end
487
+
488
+ def handle_value obj
489
+ end
490
+
491
+ def handle_argument obj
492
+ end
493
+
494
+ def handle_field obj
495
+ end
496
+
497
+ def handle_object_field obj
498
+ end
499
+
500
+ def handle_int_value obj
501
+ end
502
+
503
+ def handle_float_value obj
504
+ end
505
+
506
+ def handle_string_value obj
507
+ end
508
+
509
+ def handle_boolean_value obj
510
+ end
511
+
512
+ def handle_null_value obj
513
+ end
514
+
515
+ def handle_enum_value obj
516
+ end
517
+
518
+ def handle_list_value obj
519
+ end
520
+
521
+ def handle_object_value obj
522
+ end
523
+
524
+ def handle_directive obj
525
+ end
526
+
527
+ def handle_type_condition obj
528
+ end
529
+
530
+ def handle_inline_fragment obj
531
+ end
532
+
533
+ def handle_fragment_spread obj
534
+ end
535
+
536
+ def handle_fragment_definition obj
537
+ end
538
+
539
+ def handle_root_operation_type_definition obj
540
+ end
541
+
542
+ def handle_schema_definition obj
543
+ end
544
+
545
+ def handle_field_definition obj
546
+ end
547
+
548
+ def handle_input_value_definition obj
549
+ end
550
+
551
+ def handle_object_type_definition obj
552
+ end
553
+
554
+ def handle_interface_type_definition obj
555
+ end
556
+
557
+ def handle_union_type_definition obj
558
+ end
559
+
560
+ def handle_scalar_type_definition obj
561
+ end
562
+
563
+ def handle_enum_value_definition obj
564
+ end
565
+
566
+ def handle_enum_type_definition obj
567
+ end
568
+
569
+ def handle_input_object_type_definition obj
570
+ end
571
+
572
+ def handle_object_type_extension obj
573
+ end
574
+
575
+ def handle_executable_directive_location obj
576
+ end
577
+
578
+ def handle_type_system_directive_location obj
579
+ end
580
+
581
+ def handle_directive_definition obj
582
+ end
583
+
584
+ def handle_scalar_type_extension obj
585
+ end
586
+
587
+ def handle_interface_type_extension obj
588
+ end
589
+
590
+ def handle_union_type_extension obj
591
+ end
592
+
593
+ def handle_enum_type_extension obj
594
+ end
595
+
596
+ def handle_input_object_type_extension obj
597
+ end
598
+
599
+ end
600
+
601
+ module NullFold
602
+
603
+ def handle_document obj, _
604
+ end
605
+
606
+ def handle_operation_definition obj, _
607
+ end
608
+
609
+ def handle_variable obj, _
610
+ end
611
+
612
+ def handle_named_type obj, _
613
+ end
614
+
615
+ def handle_not_null_type obj, _
616
+ end
617
+
618
+ def handle_list_type obj, _
619
+ end
620
+
621
+ def handle_variable_definition obj, _
622
+ end
623
+
624
+ def handle_value obj, _
625
+ end
626
+
627
+ def handle_argument obj, _
628
+ end
629
+
630
+ def handle_field obj, _
631
+ end
632
+
633
+ def handle_object_field obj, _
634
+ end
635
+
636
+ def handle_int_value obj, _
637
+ end
638
+
639
+ def handle_float_value obj, _
640
+ end
641
+
642
+ def handle_string_value obj, _
643
+ end
644
+
645
+ def handle_boolean_value obj, _
646
+ end
647
+
648
+ def handle_null_value obj, _
649
+ end
650
+
651
+ def handle_enum_value obj, _
652
+ end
653
+
654
+ def handle_list_value obj, _
655
+ end
656
+
657
+ def handle_object_value obj, _
658
+ end
659
+
660
+ def handle_directive obj, _
661
+ end
662
+
663
+ def handle_type_condition obj, _
664
+ end
665
+
666
+ def handle_inline_fragment obj, _
667
+ end
668
+
669
+ def handle_fragment_spread obj, _
670
+ end
671
+
672
+ def handle_fragment_definition obj, _
673
+ end
674
+
675
+ def handle_root_operation_type_definition obj, _
676
+ end
677
+
678
+ def handle_schema_definition obj, _
679
+ end
680
+
681
+ def handle_field_definition obj, _
682
+ end
683
+
684
+ def handle_input_value_definition obj, _
685
+ end
686
+
687
+ def handle_object_type_definition obj, _
688
+ end
689
+
690
+ def handle_interface_type_definition obj, _
691
+ end
692
+
693
+ def handle_union_type_definition obj, _
694
+ end
695
+
696
+ def handle_scalar_type_definition obj, _
697
+ end
698
+
699
+ def handle_enum_value_definition obj, _
700
+ end
701
+
702
+ def handle_enum_type_definition obj, _
703
+ end
704
+
705
+ def handle_input_object_type_definition obj, _
706
+ end
707
+
708
+ def handle_object_type_extension obj, _
709
+ end
710
+
711
+ def handle_executable_directive_location obj, _
712
+ end
713
+
714
+ def handle_type_system_directive_location obj, _
715
+ end
716
+
717
+ def handle_directive_definition obj, _
718
+ end
719
+
720
+ def handle_scalar_type_extension obj, _
721
+ end
722
+
723
+ def handle_interface_type_extension obj, _
724
+ end
725
+
726
+ def handle_union_type_extension obj, _
727
+ end
728
+
729
+ def handle_enum_type_extension obj, _
730
+ end
731
+
732
+ def handle_input_object_type_extension obj, _
733
+ end
734
+
735
+ end
464
736
  end
465
737
  end
@@ -28,6 +28,20 @@ module TinyGQL
28
28
  <%- end -%>
29
29
  seed
30
30
  end
31
+ <% end %>
32
+ end
33
+
34
+ module Null
35
+ <% nodes.each do |node| %>
36
+ def handle_<%= node.human_name %> obj
37
+ end
38
+ <% end %>
39
+ end
40
+
41
+ module NullFold
42
+ <% nodes.each do |node| %>
43
+ def handle_<%= node.human_name %> obj, _
44
+ end
31
45
  <% end %>
32
46
  end
33
47
  end
data/test/lexer_test.rb CHANGED
@@ -21,7 +21,7 @@ module TinyGQL
21
21
  lexer = Lexer.new punc
22
22
  token = lexer.next_token
23
23
  expected = PUNC_LUT[punc]
24
- assert_equal(expected || [:ON, "on"], token)
24
+ assert_equal(expected, token)
25
25
  end
26
26
  end
27
27
 
@@ -179,5 +179,28 @@ eod
179
179
 
180
180
  assert_equal words.map { |x| [x.upcase.to_sym, x] }, toks
181
181
  end
182
+
183
+ def test_looks_like_kw
184
+ words = ["fragment", "fragments"]
185
+ doc = words.join(" ")
186
+
187
+ lexer = Lexer.new doc
188
+ toks = []
189
+ while tok = lexer.advance
190
+ toks << tok
191
+ end
192
+
193
+ assert_equal [:FRAGMENT, :IDENTIFIER], toks
194
+ end
195
+
196
+ def test_num_with_dots
197
+ lexer = Lexer.new "1...2"
198
+ toks = []
199
+ while tok = lexer.advance
200
+ toks << tok
201
+ end
202
+
203
+ assert_equal [:INT, :ELLIPSIS, :INT], toks
204
+ end
182
205
  end
183
206
  end
data/test/parser_test.rb CHANGED
@@ -58,8 +58,8 @@ mutation {
58
58
  eod
59
59
  parser = Parser.new doc
60
60
  ast = parser.parse
61
- expected = ["likeStory", "story", "likeCount"].map { |str| doc.index(str) + str.bytesize }
62
- assert_equal expected, ast.find_all(&:field?).map(&:pos)
61
+ expected = ["likeStory", "story", "likeCount"].map { |str| doc.index(str) }
62
+ assert_equal expected, ast.find_all(&:field?).map(&:start)
63
63
  assert_equal [2, 3, 4], ast.find_all(&:field?).map { |n| n.line(doc) }
64
64
  end
65
65
 
@@ -165,6 +165,54 @@ eod
165
165
  assert_equal 1, node.arguments.length
166
166
  end
167
167
 
168
+ def test_null
169
+ doc = <<-eod
170
+ mutation {
171
+ a: likeStory(storyID: 12345) {
172
+ b: story {
173
+ c: likeCount
174
+ }
175
+ }
176
+ }
177
+ eod
178
+ viz = Module.new do
179
+ extend TinyGQL::Visitors::Null
180
+
181
+ def self.handle_field obj
182
+ true
183
+ end
184
+ end
185
+ parser = Parser.new doc
186
+ ast = parser.parse
187
+ ast.each { |node|
188
+ assert_equal(node.field?, !!node.accept(viz))
189
+ }
190
+ end
191
+
192
+ def test_null_fold
193
+ doc = <<-eod
194
+ mutation {
195
+ a: likeStory(storyID: 12345) {
196
+ b: story {
197
+ c: likeCount
198
+ }
199
+ }
200
+ }
201
+ eod
202
+ viz = Module.new do
203
+ extend TinyGQL::Visitors::NullFold
204
+
205
+ def self.handle_field obj, x
206
+ x
207
+ end
208
+ end
209
+ parser = Parser.new doc
210
+ ast = parser.parse
211
+ ast.each { |node|
212
+ assert_equal(node.field?, !!node.fold(viz, true))
213
+ }
214
+ end
215
+
168
216
  def test_multiple_implements
169
217
  doc = <<-eod
170
218
  type SomeType implements a, b, c {
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tinygql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Patterson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-28 00:00:00.000000000 Z
11
+ date: 2023-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -53,6 +53,7 @@ files:
53
53
  - Rakefile
54
54
  - benchmark/fixtures/negotiate.gql
55
55
  - bin/bench.rb
56
+ - bin/make_hash.rb
56
57
  - bin/profile.rb
57
58
  - lib/tinygql.rb
58
59
  - lib/tinygql/lexer.rb