tinygql 0.1.4 → 0.2.0

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: 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