kql 1.0.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 +7 -0
- data/.github/workflows/main.yml +16 -0
- data/.gitignore +10 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +31 -0
- data/LICENSE.txt +21 -0
- data/README.md +52 -0
- data/Rakefile +17 -0
- data/bin/console +15 -0
- data/bin/racc +29 -0
- data/bin/rake +29 -0
- data/bin/setup +8 -0
- data/kql.gemspec +30 -0
- data/lib/kql/accessor.rb +99 -0
- data/lib/kql/combinator.rb +41 -0
- data/lib/kql/filter.rb +59 -0
- data/lib/kql/kql.yy +98 -0
- data/lib/kql/mapping.rb +23 -0
- data/lib/kql/matcher.rb +86 -0
- data/lib/kql/operator.rb +57 -0
- data/lib/kql/query.rb +77 -0
- data/lib/kql/selector.rb +41 -0
- data/lib/kql/tokenizer.rb +302 -0
- data/lib/kql/version.rb +5 -0
- data/lib/kql.rb +31 -0
- metadata +99 -0
data/lib/kql/mapping.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative './query'
|
2
|
+
|
3
|
+
module KQL
|
4
|
+
class Mapping < Query
|
5
|
+
attr_accessor :mapping
|
6
|
+
|
7
|
+
def initialize(alternatives, mapping)
|
8
|
+
super(alternatives)
|
9
|
+
@mapping = mapping
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute(document)
|
13
|
+
nodes = super
|
14
|
+
nodes.map { |node| mapping.execute(node) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
return false unless other.is_a?(Mapping)
|
19
|
+
|
20
|
+
super(other) && other.mapping = mapping
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/kql/matcher.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
module KQL
|
2
|
+
class Matcher
|
3
|
+
singleton :Any, Matcher do
|
4
|
+
def match?(node)
|
5
|
+
true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
singleton :AnyTag, Matcher do
|
10
|
+
def match?(node)
|
11
|
+
!node.type.nil?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Tag < Matcher
|
16
|
+
attr_reader :tag
|
17
|
+
alias value tag
|
18
|
+
|
19
|
+
def initialize(tag)
|
20
|
+
@tag = tag
|
21
|
+
end
|
22
|
+
|
23
|
+
def match?(node)
|
24
|
+
node.type == tag
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
return false unless other.is_a?(Tag)
|
29
|
+
|
30
|
+
other.tag == tag
|
31
|
+
end
|
32
|
+
|
33
|
+
def coerce(a)
|
34
|
+
case a
|
35
|
+
when ::KDL::Node, ::KDL::Value then a.type
|
36
|
+
else a
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Value < Matcher
|
42
|
+
attr_reader :value
|
43
|
+
|
44
|
+
def initialize(value)
|
45
|
+
@value = value
|
46
|
+
end
|
47
|
+
|
48
|
+
def ==(other)
|
49
|
+
return false unless other.is_a?(Value)
|
50
|
+
|
51
|
+
other.value == value
|
52
|
+
end
|
53
|
+
|
54
|
+
def coerce(a)
|
55
|
+
case a
|
56
|
+
when ::KDL::Value then a.value
|
57
|
+
else a
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class Comparison < Matcher
|
63
|
+
attr_reader :accessor, :operator, :value
|
64
|
+
|
65
|
+
def initialize(accessor, operator, value)
|
66
|
+
@accessor = accessor
|
67
|
+
@operator = operator
|
68
|
+
@value = value
|
69
|
+
end
|
70
|
+
|
71
|
+
def match?(node)
|
72
|
+
return false unless accessor.match?(node)
|
73
|
+
|
74
|
+
operator.execute(value.coerce(accessor.execute(node)), value.value)
|
75
|
+
end
|
76
|
+
|
77
|
+
def ==(other)
|
78
|
+
return false unless other.is_a?(Comparison)
|
79
|
+
|
80
|
+
other.accessor == accessor &&
|
81
|
+
other.operator == operator &&
|
82
|
+
other.value == value
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/kql/operator.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module KQL
|
2
|
+
class Operator
|
3
|
+
singleton :Equals, Operator do
|
4
|
+
def execute(a, b)
|
5
|
+
a == b
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
singleton :NotEquals, Operator do
|
10
|
+
def execute(a, b)
|
11
|
+
a != b
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
singleton :GreaterThanOrEqual, Operator do
|
16
|
+
def execute(a, b)
|
17
|
+
a >= b
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
singleton :GreaterThan, Operator do
|
22
|
+
def execute(a, b)
|
23
|
+
a > b
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
singleton :LessThanOrEqual, Operator do
|
28
|
+
def execute(a, b)
|
29
|
+
a <= b
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
singleton :LessThan, Operator do
|
34
|
+
def execute(a, b)
|
35
|
+
a < b
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
singleton :StartsWith, Operator do
|
40
|
+
def execute(a, b)
|
41
|
+
a.start_with?(b)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
singleton :EndsWith, Operator do
|
46
|
+
def execute(a, b)
|
47
|
+
a.end_with?(b)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
singleton :Includes, Operator do
|
52
|
+
def execute(a, b)
|
53
|
+
a.include?(b)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/kql/query.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
module KQL
|
2
|
+
class Query
|
3
|
+
attr_reader :alternatives
|
4
|
+
|
5
|
+
def initialize(alternatives)
|
6
|
+
@alternatives = alternatives
|
7
|
+
end
|
8
|
+
|
9
|
+
def ==(other)
|
10
|
+
return false unless other.is_a?(Query)
|
11
|
+
|
12
|
+
other.alternatives == alternatives
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(document)
|
16
|
+
alternatives.flat_map do |alt|
|
17
|
+
alt.execute(TopContext.new(document))
|
18
|
+
.nodes
|
19
|
+
.uniq { |n| n.__id__ }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
class Context
|
26
|
+
attr_accessor :selected_nodes
|
27
|
+
|
28
|
+
def initialize(selected_nodes)
|
29
|
+
@selected_nodes = selected_nodes
|
30
|
+
end
|
31
|
+
|
32
|
+
def nodes
|
33
|
+
selected_nodes.map(&:node)
|
34
|
+
end
|
35
|
+
|
36
|
+
def children(**kwargs)
|
37
|
+
nodes.flat_map do |node|
|
38
|
+
node.children
|
39
|
+
.each_with_index
|
40
|
+
.map { |n, i| Query::SelectedNode.new(n, node.children, i, **kwargs) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def top?
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class TopContext < Context
|
50
|
+
attr_accessor :document
|
51
|
+
|
52
|
+
def initialize(document)
|
53
|
+
@document = document
|
54
|
+
super(children)
|
55
|
+
end
|
56
|
+
|
57
|
+
def children(**kwargs)
|
58
|
+
document.nodes.each_with_index.map { |n, i| Query::SelectedNode.new(n, document.nodes, i, **kwargs) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def top?
|
62
|
+
true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class SelectedNode
|
67
|
+
attr_accessor :node, :siblings, :index, :stop
|
68
|
+
|
69
|
+
def initialize(node, siblings, index, stop: false)
|
70
|
+
@node = node
|
71
|
+
@siblings = siblings
|
72
|
+
@index = index
|
73
|
+
@stop = stop
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/kql/selector.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module KQL
|
2
|
+
class Selector
|
3
|
+
attr_reader :filter
|
4
|
+
|
5
|
+
def initialize(filter)
|
6
|
+
@filter = filter
|
7
|
+
end
|
8
|
+
|
9
|
+
def ==(other)
|
10
|
+
return false unless other.class == Selector
|
11
|
+
|
12
|
+
other.filter == filter
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(context)
|
16
|
+
filter.execute(context)
|
17
|
+
end
|
18
|
+
|
19
|
+
class Combined < Selector
|
20
|
+
attr_reader :combinator, :selector
|
21
|
+
|
22
|
+
def initialize(filter, combinator, selector)
|
23
|
+
super(filter)
|
24
|
+
@combinator = combinator
|
25
|
+
@selector = selector
|
26
|
+
end
|
27
|
+
|
28
|
+
def ==(other)
|
29
|
+
return false unless other.is_a?(Combined)
|
30
|
+
|
31
|
+
other.filter == filter &&
|
32
|
+
other.combinator == combinator &&
|
33
|
+
other.selector == selector
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute(context)
|
37
|
+
combinator.execute(super, selector)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,302 @@
|
|
1
|
+
module KQL
|
2
|
+
class Tokenizer
|
3
|
+
class Error < StandardError
|
4
|
+
def initialize(message, line, column)
|
5
|
+
super("#{message} (#{line}:#{column})")
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Token
|
10
|
+
attr_reader :type, :value, :line, :column
|
11
|
+
|
12
|
+
def initialize(type, value, line, column)
|
13
|
+
@type = type
|
14
|
+
@value = value
|
15
|
+
@line = line
|
16
|
+
@column = column
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other )
|
20
|
+
return false unless other.is_a?(Token)
|
21
|
+
return false unless type == other.type && value == other.value
|
22
|
+
|
23
|
+
if line && other.line
|
24
|
+
return false unless line == other.line
|
25
|
+
end
|
26
|
+
if column && other.column
|
27
|
+
return false unless column == other.column
|
28
|
+
end
|
29
|
+
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
"#{value.inspect} (#{line || '?'}:#{column || '?'})"
|
35
|
+
end
|
36
|
+
alias inspect to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
attr_reader :index
|
40
|
+
|
41
|
+
SYMBOLS = {
|
42
|
+
'(' => :LPAREN,
|
43
|
+
')' => :RPAREN,
|
44
|
+
'[' => :LBRACKET,
|
45
|
+
']' => :RBRACKET,
|
46
|
+
',' => :COMMA
|
47
|
+
}
|
48
|
+
|
49
|
+
WHITESPACE = ["\u0009", "\u0020", "\u00A0", "\u1680",
|
50
|
+
"\u2000", "\u2001", "\u2002", "\u2003",
|
51
|
+
"\u2004", "\u2005", "\u2006", "\u2007",
|
52
|
+
"\u2008", "\u2009", "\u200A", "\u202F",
|
53
|
+
"\u205F", "\u3000", ' ']
|
54
|
+
|
55
|
+
NEWLINES = ["\u000A", "\u0085", "\u000C", "\u2028", "\u2029"]
|
56
|
+
|
57
|
+
NON_IDENTIFIER_CHARS = Regexp.escape "#{SYMBOLS.keys.join('')}()/\\<>[]\","
|
58
|
+
IDENTIFIER_CHARS = /[^#{NON_IDENTIFIER_CHARS}\x0-\x20]/
|
59
|
+
INITIAL_IDENTIFIER_CHARS = /[^#{NON_IDENTIFIER_CHARS}0-9\x0-\x20]/
|
60
|
+
|
61
|
+
def initialize(str, start = 0)
|
62
|
+
@str = str
|
63
|
+
@context = nil
|
64
|
+
@index = start
|
65
|
+
@buffer = ''
|
66
|
+
@previous_context = nil
|
67
|
+
@line = 1
|
68
|
+
@column = 1
|
69
|
+
end
|
70
|
+
|
71
|
+
def next_token
|
72
|
+
@context = nil
|
73
|
+
@previous_context = nil
|
74
|
+
@line_at_start = @line
|
75
|
+
@column_at_start = @column
|
76
|
+
loop do
|
77
|
+
c = @str[@index]
|
78
|
+
n = @str[@index + 1]
|
79
|
+
case @context
|
80
|
+
when nil
|
81
|
+
case c
|
82
|
+
when '"'
|
83
|
+
self.context = :string
|
84
|
+
@buffer = ''
|
85
|
+
traverse(1)
|
86
|
+
when /[0-9\-]/
|
87
|
+
self.context = :number
|
88
|
+
traverse(1)
|
89
|
+
@buffer = c
|
90
|
+
when '='
|
91
|
+
if n == '>'
|
92
|
+
return token(:MAP, '=>').tap { traverse(2) }
|
93
|
+
else
|
94
|
+
return token(:EQUALS, c).tap { traverse(1) }
|
95
|
+
end
|
96
|
+
when '>'
|
97
|
+
if n == '='
|
98
|
+
return token(:GTE, '>=').tap { traverse(2) }
|
99
|
+
else
|
100
|
+
return token(:GT, c).tap { traverse(1) }
|
101
|
+
end
|
102
|
+
when '<'
|
103
|
+
if n == '='
|
104
|
+
return token(:LTE, '<=').tap { traverse(2) }
|
105
|
+
else
|
106
|
+
return token(:LT, c).tap { traverse(1) }
|
107
|
+
end
|
108
|
+
when '|'
|
109
|
+
if n == '|'
|
110
|
+
return token(:OR, '||').tap { traverse(2) }
|
111
|
+
else
|
112
|
+
self.context = :ident
|
113
|
+
@buffer = c
|
114
|
+
traverse(1)
|
115
|
+
end
|
116
|
+
when '^'
|
117
|
+
if n == '='
|
118
|
+
return token(:STARTS_WITH, '^=').tap { traverse(2) }
|
119
|
+
else
|
120
|
+
self.context = :ident
|
121
|
+
@buffer = c
|
122
|
+
traverse(1)
|
123
|
+
end
|
124
|
+
when '$'
|
125
|
+
if n == '='
|
126
|
+
return token(:ENDS_WITH, '$=').tap { traverse(2) }
|
127
|
+
else
|
128
|
+
self.context = :ident
|
129
|
+
@buffer = c
|
130
|
+
traverse(1)
|
131
|
+
end
|
132
|
+
when '*'
|
133
|
+
if n == '='
|
134
|
+
return token(:INCLUDES, '*=').tap { traverse(2) }
|
135
|
+
else
|
136
|
+
self.context = :ident
|
137
|
+
@buffer = c
|
138
|
+
traverse(1)
|
139
|
+
end
|
140
|
+
when '+'
|
141
|
+
case n
|
142
|
+
when /[0-9]/
|
143
|
+
self.context = :number
|
144
|
+
traverse(1)
|
145
|
+
@buffer = c
|
146
|
+
when IDENTIFIER_CHARS
|
147
|
+
self.context = :ident
|
148
|
+
@buffer = c
|
149
|
+
traverse(1)
|
150
|
+
else
|
151
|
+
return token(:PLUS, '+').tap { traverse(1) }
|
152
|
+
end
|
153
|
+
when '~'
|
154
|
+
case n
|
155
|
+
when IDENTIFIER_CHARS
|
156
|
+
self.context = :ident
|
157
|
+
@buffer = c
|
158
|
+
traverse(1)
|
159
|
+
else
|
160
|
+
return token(:TILDE, '~').tap { traverse(1) }
|
161
|
+
end
|
162
|
+
when '!'
|
163
|
+
case n
|
164
|
+
when '='
|
165
|
+
return token(:NOT_EQUALS, '!=').tap { traverse(2) }
|
166
|
+
else
|
167
|
+
self.context = :ident
|
168
|
+
@buffer = c
|
169
|
+
traverse(1)
|
170
|
+
end
|
171
|
+
when *SYMBOLS.keys
|
172
|
+
return token(SYMBOLS[c], c).tap { traverse(1) }
|
173
|
+
when *WHITESPACE
|
174
|
+
traverse(1)
|
175
|
+
when *NEWLINES
|
176
|
+
traverse(1)
|
177
|
+
new_line
|
178
|
+
when INITIAL_IDENTIFIER_CHARS
|
179
|
+
self.context = :ident
|
180
|
+
@buffer = c
|
181
|
+
traverse(1)
|
182
|
+
when nil
|
183
|
+
return [false, nil]
|
184
|
+
else
|
185
|
+
raise_error "Unexpected `#{c}'"
|
186
|
+
end
|
187
|
+
when :ident
|
188
|
+
case c
|
189
|
+
when IDENTIFIER_CHARS
|
190
|
+
traverse(1)
|
191
|
+
@buffer += c
|
192
|
+
else
|
193
|
+
case @buffer
|
194
|
+
when 'true' then return token(:TRUE, true)
|
195
|
+
when 'false' then return token(:FALSE, false)
|
196
|
+
when 'null' then return token(:NULL, nil)
|
197
|
+
when 'top', 'name', 'tag', 'props', 'values'
|
198
|
+
if c == '(' && n == ')'
|
199
|
+
return token(@buffer.upcase.to_sym, "#{@buffer}()").tap { traverse(2) }
|
200
|
+
end
|
201
|
+
when 'val'
|
202
|
+
return token(:VAL, @buffer) if c == '('
|
203
|
+
when 'prop'
|
204
|
+
return token(:PROP, @buffer) if c == '('
|
205
|
+
end
|
206
|
+
return token(:IDENT, @buffer)
|
207
|
+
end
|
208
|
+
when :string
|
209
|
+
case c
|
210
|
+
when '\\'
|
211
|
+
@buffer += c
|
212
|
+
@buffer += @str[@index + 1]
|
213
|
+
traverse(2)
|
214
|
+
when '"'
|
215
|
+
return token(:STRING, convert_escapes(@buffer)).tap { traverse(1) }
|
216
|
+
when nil
|
217
|
+
raise_error 'Unterminated string literal'
|
218
|
+
else
|
219
|
+
@buffer += c
|
220
|
+
traverse(1)
|
221
|
+
end
|
222
|
+
when :number
|
223
|
+
case c
|
224
|
+
when /[0-9.\-+_eE]/
|
225
|
+
traverse(1)
|
226
|
+
@buffer += c
|
227
|
+
else
|
228
|
+
return parse_number(@buffer)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
private
|
235
|
+
|
236
|
+
def token(type, value)
|
237
|
+
[type, Token.new(type, value, @line_at_start, @column_at_start)]
|
238
|
+
end
|
239
|
+
|
240
|
+
def traverse(count = 1)
|
241
|
+
@column += count
|
242
|
+
@index += count
|
243
|
+
end
|
244
|
+
|
245
|
+
def raise_error(message)
|
246
|
+
raise Error.new(message, @line, @column)
|
247
|
+
end
|
248
|
+
|
249
|
+
def new_line
|
250
|
+
@column = 1
|
251
|
+
@line += 1
|
252
|
+
end
|
253
|
+
|
254
|
+
def context=(new_context)
|
255
|
+
@previous_context = @context
|
256
|
+
@context = new_context
|
257
|
+
end
|
258
|
+
|
259
|
+
def parse_number(string)
|
260
|
+
return parse_float(string) if string =~ /[.E]/i
|
261
|
+
|
262
|
+
token(:INTEGER, Integer(munch_underscores(string), 10))
|
263
|
+
end
|
264
|
+
|
265
|
+
def parse_float(string)
|
266
|
+
string = munch_underscores(string)
|
267
|
+
|
268
|
+
value = Float(string)
|
269
|
+
if value.infinite? || (value.zero? && exponent.to_i < 0)
|
270
|
+
token(:FLOAT, BigDecimal(string))
|
271
|
+
else
|
272
|
+
token(:FLOAT, value)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def munch_underscores(string)
|
277
|
+
string.chomp('_').squeeze('_')
|
278
|
+
end
|
279
|
+
|
280
|
+
def convert_escapes(string)
|
281
|
+
string.gsub(/\\[^u]/) do |m|
|
282
|
+
case m
|
283
|
+
when '\n' then "\n"
|
284
|
+
when '\r' then "\r"
|
285
|
+
when '\t' then "\t"
|
286
|
+
when '\\\\' then '\\'
|
287
|
+
when '\"' then '"'
|
288
|
+
when '\b' then "\b"
|
289
|
+
when '\f' then "\f"
|
290
|
+
when '\/' then '/'
|
291
|
+
else raise_error "Unexpected escape #{m.inspect}"
|
292
|
+
end
|
293
|
+
end.gsub(/\\u\{[0-9a-fA-F]{0,6}\}/) do |m|
|
294
|
+
i = Integer(m[3..-2], 16)
|
295
|
+
if i < 0 || i > 0x10FFFF
|
296
|
+
raise_error "Invalid code point #{u}"
|
297
|
+
end
|
298
|
+
i.chr(Encoding::UTF_8)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
data/lib/kql/version.rb
ADDED
data/lib/kql.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Class.module_eval do
|
4
|
+
def singleton(classname, superclass = nil, &block)
|
5
|
+
klass = Class.new(superclass, &block)
|
6
|
+
const_set(:"#{classname}Impl", klass)
|
7
|
+
const_set(classname, klass.new)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module KQL
|
12
|
+
def self.parse_query(query)
|
13
|
+
Parser.new.parse(query)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.query_document(document, query)
|
17
|
+
parse_query(query).execute(document)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
require_relative "kql/version"
|
22
|
+
require_relative "kql/tokenizer"
|
23
|
+
require_relative "kql/query"
|
24
|
+
require_relative "kql/filter"
|
25
|
+
require_relative "kql/combinator"
|
26
|
+
require_relative "kql/selector"
|
27
|
+
require_relative "kql/matcher"
|
28
|
+
require_relative "kql/accessor"
|
29
|
+
require_relative "kql/operator"
|
30
|
+
require_relative "kql/mapping"
|
31
|
+
require_relative "kql/kql.tab"
|