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 +4 -4
- data/bin/bench.rb +38 -0
- data/lib/tinygql/lexer.rb +118 -40
- data/lib/tinygql/nodes.rb +130 -81
- data/lib/tinygql/nodes.rb.erb +15 -2
- data/lib/tinygql/parser.rb +108 -47
- data/lib/tinygql/version.rb +1 -1
- data/test/lexer_test.rb +78 -0
- data/test/parser_test.rb +17 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 114da94b6827645fb92f0f3e85b9e4f34b64aaee8449c143217cc75fc903ab3c
|
4
|
+
data.tar.gz: 27031f9423d30f0453a6737be1184cb8c724a6290bc432b7e35dea255fa549a5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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"
|
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
|
-
|
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(
|
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
|
-
|
95
|
-
|
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
|
111
|
-
when
|
112
|
-
when @
|
113
|
-
|
114
|
-
|
115
|
-
when @scan.
|
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
|
-
|
171
|
+
@scan.getch
|
172
|
+
:UNKNOWN_CHAR
|
118
173
|
end
|
119
174
|
end
|
120
175
|
|
121
|
-
|
176
|
+
def token_value
|
177
|
+
@string.byteslice(@scan.pos - @scan.matched_size, @scan.matched_size)
|
178
|
+
end
|
122
179
|
|
123
|
-
def
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
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
|
-
|
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
|