tinygql 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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