kql 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|