tinygql 0.1.4 → 0.2.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: ef7a07443c070f78f1ad6324890611e1ea70298c7d67eac591911c208b42aa0f
4
- data.tar.gz: 3311b13953c075059a737b46f82f50aa3fd2428d844499c64354a285aa2774cd
3
+ metadata.gz: 114da94b6827645fb92f0f3e85b9e4f34b64aaee8449c143217cc75fc903ab3c
4
+ data.tar.gz: 27031f9423d30f0453a6737be1184cb8c724a6290bc432b7e35dea255fa549a5
5
5
  SHA512:
6
- metadata.gz: 814659e285e05236a4861931cf548eabab4abeaccdbc5b959adc12e3a3e6f6b9a67ea5626117ae44f46a9316c8d3210adafbf269730aec23b8c9d5ac0a538832
7
- data.tar.gz: 3603440c4c6aa2aed7c361439299b8218b2f01a2c5ea96ed100da5028ac6fe391d62322a4431124861db7de50c60c69fd50d3da92178c12ef04dd58fbe63e2d3
6
+ metadata.gz: a4362f8d3c3d638c715c4686ec9714d59f59d411be390cf115d0b7e8bc4f710fcbfe935ca058092f24935964fd29905643e6f1f4edeb84e238e1af0d5d2eff4e
7
+ data.tar.gz: 5ec4f82ff306221b2c8e008b3bb76feb1c55917ab26334e1531f7eb8e58f03e44a29901fe92765585cfa195c41a107fdd13337b3bb844f13bb9985f1394f8ee0
data/bin/bench.rb CHANGED
@@ -20,3 +20,41 @@ Benchmark.ips do |x|
20
20
  end
21
21
  end
22
22
  end
23
+
24
+ module Benchmark
25
+ def self.allocs; yield Allocs; end
26
+ end
27
+
28
+ class Allocs
29
+ def self.report name, &block
30
+ allocs = nil
31
+
32
+ 2.times do # 2 times to heat caches
33
+ allocs = 10.times.map {
34
+ x = GC.stat(:total_allocated_objects)
35
+ yield
36
+ GC.stat(:total_allocated_objects) - x
37
+ }.inject(:+) / 10
38
+ end
39
+
40
+ puts name.rjust(20) + allocs.to_s.rjust(10)
41
+ end
42
+ end
43
+
44
+ print "#" * 30
45
+ print " ALLOCATIONS "
46
+ puts "#" * 30
47
+
48
+ Benchmark.allocs do |x|
49
+ x.report "kitchen-sink" do
50
+ TinyGQL.parse source
51
+ end
52
+
53
+ files.each do |file_name|
54
+ data = File.read file_name
55
+ name = File.basename(file_name, File.extname(file_name))
56
+ x.report name do
57
+ TinyGQL.parse data
58
+ end
59
+ end
60
+ end
data/lib/tinygql/lexer.rb CHANGED
@@ -16,27 +16,29 @@ module TinyGQL
16
16
  FLOAT_EXP = /[eE][+-]?[0-9]+/
17
17
  NUMERIC = /#{INT}(#{FLOAT_DECIMAL}#{FLOAT_EXP}|#{FLOAT_DECIMAL}|#{FLOAT_EXP})?/
18
18
 
19
- KEYWORDS = {
20
- "on" => :ON,
21
- "fragment" => :FRAGMENT,
22
- "true" => :TRUE,
23
- "false" => :FALSE,
24
- "null" => :NULL,
25
- "query" => :QUERY,
26
- "mutation" => :MUTATION,
27
- "subscription" => :SUBSCRIPTION,
28
- "schema" => :SCHEMA,
29
- "scalar" => :SCALAR,
30
- "type" => :TYPE,
31
- "extend" => :EXTEND,
32
- "implements" => :IMPLEMENTS,
33
- "interface" => :INTERFACE,
34
- "union" => :UNION,
35
- "enum" => :ENUM,
36
- "input" => :INPUT,
37
- "directive" => :DIRECTIVE,
38
- "repeatable" => :REPEATABLE
39
- }.freeze
19
+ KEYWORDS = [
20
+ "on",
21
+ "fragment",
22
+ "true",
23
+ "false",
24
+ "null",
25
+ "query",
26
+ "mutation",
27
+ "subscription",
28
+ "schema",
29
+ "scalar",
30
+ "type",
31
+ "extend",
32
+ "implements",
33
+ "interface",
34
+ "union",
35
+ "enum",
36
+ "input",
37
+ "directive",
38
+ "repeatable"
39
+ ].freeze
40
+
41
+ KW_RE = /#{Regexp.union(KEYWORDS.sort)}\b/
40
42
 
41
43
  module Literals
42
44
  LCURLY = '{'
@@ -48,13 +50,14 @@ module TinyGQL
48
50
  COLON = ':'
49
51
  VAR_SIGN = '$'
50
52
  DIR_SIGN = '@'
51
- ELLIPSIS = '...'
52
53
  EQUALS = '='
53
54
  BANG = '!'
54
55
  PIPE = '|'
55
56
  AMP = '&'
56
57
  end
57
58
 
59
+ ELLIPSIS = '...'
60
+
58
61
  include Literals
59
62
 
60
63
  QUOTE = '"'
@@ -68,8 +71,8 @@ module TinyGQL
68
71
  ESCAPED_QUOTE = /\\"/;
69
72
  STRING_CHAR = /#{ESCAPED_QUOTE}|[^"\\]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/
70
73
 
71
- LIT_NAME_LUT = Literals.constants.each_with_object({}) { |n, o|
72
- o[Literals.const_get(n)] = n
74
+ LIT_NAME_LUT = Literals.constants.each_with_object([]) { |n, o|
75
+ o[Literals.const_get(n).ord] = n
73
76
  }
74
77
 
75
78
  LIT = Regexp.union(Literals.constants.map { |n| Literals.const_get(n) })
@@ -90,9 +93,12 @@ module TinyGQL
90
93
  def initialize string
91
94
  raise unless string.valid_encoding?
92
95
 
96
+ @string = string
93
97
  @scan = StringScanner.new string
94
- @token_name = nil
95
- @token_value = nil
98
+ end
99
+
100
+ def pos
101
+ @scan.pos
96
102
  end
97
103
 
98
104
  def line
@@ -103,31 +109,97 @@ module TinyGQL
103
109
  @scan.eos?
104
110
  end
105
111
 
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
+
106
144
  def advance
107
145
  @scan.skip(IGNORE)
108
146
 
109
147
  case
110
- when str = @scan.scan(LIT) then return emit(LIT_NAME_LUT[str], str)
111
- when str = @scan.scan(IDENTIFIER) then return emit(KEYWORDS.fetch(str, :IDENTIFIER), str)
112
- when @scan.skip(BLOCK_STRING) then return emit_block(@scan[1])
113
- when @scan.skip(QUOTED_STRING) then return emit_string(@scan[1])
114
- when str = @scan.scan(NUMERIC) then return emit(@scan[1] ? :FLOAT : :INT, str)
115
- when @scan.eos? then emit(nil, nil) and return false
148
+ when @scan.eos? then false
149
+ when @scan.skip(ELLIPSIS) then :ELLIPSIS
150
+ when tok = LIT_NAME_LUT[@string.getbyte(@scan.pos)] then
151
+ @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)
116
170
  else
117
- emit(:UNKNOWN_CHAR, @scan.getch)
171
+ @scan.getch
172
+ :UNKNOWN_CHAR
118
173
  end
119
174
  end
120
175
 
121
- attr_reader :token_name, :token_value
176
+ def token_value
177
+ @string.byteslice(@scan.pos - @scan.matched_size, @scan.matched_size)
178
+ end
122
179
 
123
- def emit token_name, token_value
124
- @token_name = token_name
125
- @token_value = token_value
126
- true
180
+ def string_value
181
+ str = token_value
182
+ block = str.start_with?('"""')
183
+ str.gsub!(/\A"*|"*\z/, '')
184
+
185
+ if block
186
+ emit_block str
187
+ else
188
+ emit_string str
189
+ end
127
190
  end
128
191
 
129
192
  def next_token
130
- advance && [@token_name, @token_value]
193
+ return unless tok = advance
194
+ val = case tok
195
+ when :STRING then string_value
196
+ when *Literals.constants
197
+ @string.byteslice(@scan.pos - 1, 1)
198
+ else
199
+ token_value
200
+ end
201
+
202
+ [tok, val]
131
203
  end
132
204
 
133
205
  # Replace any escaped unicode or whitespace with the _actual_ characters
@@ -184,7 +256,7 @@ module TinyGQL
184
256
  if !value.valid_encoding?
185
257
  emit(:BAD_UNICODE_ESCAPE, value)
186
258
  else
187
- emit(:STRING, value)
259
+ value
188
260
  end
189
261
  end
190
262
  end
@@ -242,5 +314,11 @@ module TinyGQL
242
314
  # Rebuild the string
243
315
  lines.size > 1 ? lines.join("\n") : (lines.first || "".dup)
244
316
  end
317
+
318
+ def hash key
319
+ # Constant came from perfect hash generation
320
+ m = key * 145291
321
+ (m >> 28) & 0x1f
322
+ end
245
323
  end
246
324
  end