tinygql 0.2.0 → 0.3.0

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: e6f2ac9d1c4531277689c6eec21fd29cd1e6fe9715284813b57c741e3e9bace2
4
+ data.tar.gz: b9cd18ac29f1aeea347296f0408deadddec7a902d25495e3232b472180718695
5
5
  SHA512:
6
- metadata.gz: a4362f8d3c3d638c715c4686ec9714d59f59d411be390cf115d0b7e8bc4f710fcbfe935ca058092f24935964fd29905643e6f1f4edeb84e238e1af0d5d2eff4e
7
- data.tar.gz: 5ec4f82ff306221b2c8e008b3bb76feb1c55917ab26334e1531f7eb8e58f03e44a29901fe92765585cfa195c41a107fdd13337b3bb844f13bb9985f1394f8ee0
6
+ metadata.gz: 63c931df357b6091070a7cd0adab859f7a7489f8fa1220ffa2cfb7fc6a7af6660ede87af6c3a8b804ef1a2114594aa3086a3792b5d3ef40e20ac1a4546d13120
7
+ data.tar.gz: 96e0a9164e835cb382391c9ea761cdf2405ac6f38e268034f810f8ce369d4e968ec5a2c174f22f03f50a99bd1f155449e5d9e7b46c65fc771263cbc03a8f2f8b
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
@@ -75,8 +75,6 @@ module TinyGQL
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) })
79
-
80
78
  QUOTED_STRING = %r{#{QUOTE} ((?:#{STRING_CHAR})*) #{QUOTE}}x
81
79
  BLOCK_STRING = %r{
82
80
  #{BLOCK_QUOTE}
@@ -95,11 +93,10 @@ module TinyGQL
95
93
 
96
94
  @string = string
97
95
  @scan = StringScanner.new string
96
+ @start = nil
98
97
  end
99
98
 
100
- def pos
101
- @scan.pos
102
- end
99
+ attr_reader :start
103
100
 
104
101
  def line
105
102
  @scan.string[0, @scan.pos].count("\n") + 1
@@ -109,60 +106,27 @@ module TinyGQL
109
106
  @scan.eos?
110
107
  end
111
108
 
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
109
  def advance
145
110
  @scan.skip(IGNORE)
146
111
 
112
+ @start = @scan.pos
113
+
147
114
  case
148
115
  when @scan.eos? then false
149
116
  when @scan.skip(ELLIPSIS) then :ELLIPSIS
150
- when tok = LIT_NAME_LUT[@string.getbyte(@scan.pos)] then
117
+ when tok = LIT_NAME_LUT[@string.getbyte(@start)] then
151
118
  @scan.pos += 1
152
119
  tok
153
- when @scan.skip(KW_RE) then
154
- len = @scan.matched_size
120
+ when len = @scan.skip(KW_RE) then
155
121
  return :ON if len == 2
156
122
  return :SUBSCRIPTION if len == 12
157
123
 
158
- pos = @scan.pos - len
124
+ pos = @start
159
125
 
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)
126
+ # Second 2 bytes are unique, so we'll hash on those
127
+ key = (@string.getbyte(pos + 2) << 8) | @string.getbyte(pos + 1)
164
128
 
165
- KW_LUT[hash(key)]
129
+ KW_LUT[_hash(key)]
166
130
  when @scan.skip(IDENTIFIER) then :IDENTIFIER
167
131
  when @scan.skip(BLOCK_STRING) then :STRING
168
132
  when @scan.skip(QUOTED_STRING) then :STRING
@@ -315,10 +279,41 @@ module TinyGQL
315
279
  lines.size > 1 ? lines.join("\n") : (lines.first || "".dup)
316
280
  end
317
281
 
318
- def hash key
319
- # Constant came from perfect hash generation
320
- m = key * 145291
321
- (m >> 28) & 0x1f
282
+ KW_LUT =[:INTERFACE,
283
+ :MUTATION,
284
+ :EXTEND,
285
+ :FALSE,
286
+ :ENUM,
287
+ :TRUE,
288
+ :NULL,
289
+ nil,
290
+ nil,
291
+ nil,
292
+ nil,
293
+ nil,
294
+ nil,
295
+ nil,
296
+ :QUERY,
297
+ nil,
298
+ nil,
299
+ :REPEATABLE,
300
+ :IMPLEMENTS,
301
+ :INPUT,
302
+ :TYPE,
303
+ :SCHEMA,
304
+ nil,
305
+ nil,
306
+ nil,
307
+ :DIRECTIVE,
308
+ :UNION,
309
+ nil,
310
+ nil,
311
+ :SCALAR,
312
+ nil,
313
+ :FRAGMENT]
314
+
315
+ def _hash key
316
+ (key * 18592990) >> 27 & 0x1f
322
317
  end
323
318
  end
324
319
  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.0'
3
3
  end
data/test/lexer_test.rb CHANGED
@@ -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
 
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.0
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-08-30 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