lkml 0.1.0 → 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/.github/workflows/ci.yml +28 -0
- data/.gitignore +57 -0
- data/.rubocop.yml +25 -0
- data/.ruby-version +1 -0
- data/CONTRIBUTING.md +56 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +76 -0
- data/LICENSE.md +1 -2
- data/README.md +81 -7
- data/Rakefile +12 -0
- data/bin/lkml +6 -0
- data/lib/lkml/cli.rb +44 -0
- data/lib/lkml/keys.rb +64 -114
- data/lib/lkml/lexer.rb +45 -68
- data/lib/lkml/parser.rb +296 -191
- data/lib/lkml/simple.rb +244 -238
- data/lib/lkml/tokens.rb +44 -129
- data/lib/lkml/tree.rb +362 -236
- data/lib/lkml/utils.rb +32 -0
- data/lib/lkml/version.rb +1 -1
- data/lib/lkml/visitors.rb +69 -64
- data/lib/lkml.rb +49 -14
- data/lkml.gemspec +35 -0
- data/script/benchmark.rb +36 -0
- data/script/download_lookml.rb +70 -0
- metadata +88 -13
data/lib/lkml/parser.rb
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require "logger"
|
|
4
4
|
|
|
5
|
-
require_relative
|
|
6
|
-
require_relative
|
|
5
|
+
require_relative "tokens"
|
|
6
|
+
require_relative "tree"
|
|
7
|
+
require_relative "utils"
|
|
7
8
|
|
|
8
9
|
module Lkml
|
|
10
|
+
unless const_defined?(:AttributeError)
|
|
11
|
+
class AttributeError < StandardError
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
DELIMITER = ". "
|
|
16
|
+
|
|
9
17
|
class CommaSeparatedValues
|
|
10
|
-
attr_accessor :trailing_comma, :leading_comma
|
|
18
|
+
attr_accessor :values, :trailing_comma, :leading_comma
|
|
11
19
|
|
|
12
20
|
def initialize
|
|
13
|
-
@
|
|
21
|
+
@values = []
|
|
14
22
|
@trailing_comma = nil
|
|
15
23
|
@leading_comma = nil
|
|
16
24
|
end
|
|
17
25
|
|
|
18
26
|
def append(value)
|
|
19
|
-
@
|
|
27
|
+
@values << value
|
|
20
28
|
end
|
|
21
29
|
|
|
22
|
-
def
|
|
23
|
-
@
|
|
30
|
+
def frozen_tuple
|
|
31
|
+
@values.dup.freeze
|
|
24
32
|
end
|
|
25
33
|
end
|
|
26
34
|
|
|
@@ -28,15 +36,22 @@ module Lkml
|
|
|
28
36
|
attr_accessor :tokens, :index, :progress, :depth, :log_debug
|
|
29
37
|
|
|
30
38
|
def initialize(stream)
|
|
31
|
-
stream.each
|
|
39
|
+
stream.each do |token|
|
|
40
|
+
raise TypeError, "Type #{token.class} for #{token} is not a valid token type." unless token.is_a?(Tokens::Token)
|
|
41
|
+
end
|
|
32
42
|
@tokens = stream
|
|
33
43
|
@index = 0
|
|
34
44
|
@progress = 0
|
|
35
45
|
@depth = -1
|
|
46
|
+
@logger = Logger.new($stderr)
|
|
47
|
+
@logger.level = Logger::WARN
|
|
48
|
+
@log_debug = false
|
|
36
49
|
end
|
|
37
50
|
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
attr_reader :logger
|
|
52
|
+
|
|
53
|
+
def jump_to_index(idx)
|
|
54
|
+
@index = idx
|
|
40
55
|
end
|
|
41
56
|
|
|
42
57
|
def peek
|
|
@@ -55,18 +70,21 @@ module Lkml
|
|
|
55
70
|
|
|
56
71
|
def consume_token_value
|
|
57
72
|
token = consume
|
|
58
|
-
raise
|
|
73
|
+
raise AttributeError unless token.is_a?(Tokens::ContentToken)
|
|
59
74
|
|
|
60
75
|
token.value
|
|
61
76
|
end
|
|
62
77
|
|
|
63
78
|
def consume_trivia(only_newlines: false)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
79
|
+
valid = if only_newlines
|
|
80
|
+
[Tokens::CommentToken, Tokens::LinebreakToken]
|
|
81
|
+
else
|
|
82
|
+
[Tokens::CommentToken,
|
|
83
|
+
Tokens::WhitespaceToken]
|
|
84
|
+
end
|
|
85
|
+
trivia = +""
|
|
68
86
|
loop do
|
|
69
|
-
break unless check(*
|
|
87
|
+
break unless check(*valid)
|
|
70
88
|
|
|
71
89
|
trivia += consume_token_value
|
|
72
90
|
end
|
|
@@ -77,262 +95,349 @@ module Lkml
|
|
|
77
95
|
mark = @index
|
|
78
96
|
consume_trivia if skip_trivia
|
|
79
97
|
|
|
98
|
+
if @log_debug
|
|
99
|
+
names = token_types.map(&:name).join(" or ")
|
|
100
|
+
tok = peek_safe
|
|
101
|
+
@logger.debug(format("%sCheck %s == %s", delimiter_repeat(1 + @depth), tok.inspect, names))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
token_types.each do |token_type|
|
|
105
|
+
unless token_type <= Tokens::Token || token_type == Tokens::Token
|
|
106
|
+
raise TypeError, "#{token_type} is not a valid token type."
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
80
110
|
token = begin
|
|
81
|
-
|
|
82
|
-
rescue
|
|
111
|
+
@tokens.fetch(@index)
|
|
112
|
+
rescue IndexError
|
|
83
113
|
nil
|
|
84
114
|
end
|
|
85
|
-
result = token_types.any? { |
|
|
86
|
-
|
|
115
|
+
result = token && token_types.any? { |t| token.is_a?(t) }
|
|
87
116
|
jump_to_index(mark) if skip_trivia
|
|
88
117
|
result
|
|
89
118
|
end
|
|
90
119
|
|
|
120
|
+
def peek_safe
|
|
121
|
+
@tokens.fetch(@index)
|
|
122
|
+
end
|
|
123
|
+
|
|
91
124
|
def parse
|
|
92
125
|
advance if check(Tokens::StreamStartToken)
|
|
93
126
|
prefix = consume_trivia
|
|
94
127
|
container = parse_container
|
|
95
128
|
suffix = consume_trivia
|
|
96
|
-
DocumentNode.new(container, prefix
|
|
129
|
+
Tree::DocumentNode.new(container, prefix, suffix)
|
|
97
130
|
end
|
|
98
131
|
|
|
99
132
|
def parse_container
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
items << block
|
|
105
|
-
next
|
|
133
|
+
backtrack_if_none do
|
|
134
|
+
if @log_debug
|
|
135
|
+
grammar = "[expression] = (block / pair / list)*"
|
|
136
|
+
@logger.debug(format("%sTry to parse %s", delimiter_repeat(@depth), grammar))
|
|
106
137
|
end
|
|
138
|
+
items = []
|
|
139
|
+
until check(Tokens::StreamEndToken, Tokens::BlockEndToken, skip_trivia: true)
|
|
140
|
+
block = parse_block
|
|
141
|
+
if block
|
|
142
|
+
items << block
|
|
143
|
+
next
|
|
144
|
+
end
|
|
107
145
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
146
|
+
pair = parse_pair
|
|
147
|
+
if pair
|
|
148
|
+
items << pair
|
|
149
|
+
next
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
list = parse_list
|
|
153
|
+
if list
|
|
154
|
+
items << list
|
|
155
|
+
next
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
token = @tokens[@progress]
|
|
159
|
+
raise SyntaxError, "Unable to find a matching expression for '#{token.id}' " \
|
|
160
|
+
"on line #{token.line_number}"
|
|
113
161
|
|
|
114
|
-
list = parse_list
|
|
115
|
-
if list
|
|
116
|
-
items << list
|
|
117
|
-
next
|
|
118
162
|
end
|
|
119
163
|
|
|
120
|
-
|
|
121
|
-
raise SyntaxError, "Unable to find a matching expression for '#{token}' on line #{token.line_number}"
|
|
164
|
+
Tree::ContainerNode.new(items: items.freeze, top_level: @depth.zero?)
|
|
122
165
|
end
|
|
123
|
-
|
|
124
|
-
ContainerNode.new(items, top_level: @depth.zero?)
|
|
125
166
|
end
|
|
126
167
|
|
|
127
168
|
def parse_block
|
|
128
|
-
|
|
129
|
-
|
|
169
|
+
backtrack_if_none do
|
|
170
|
+
if @log_debug
|
|
171
|
+
grammar = "[block] = key literal? '{' expression '}'"
|
|
172
|
+
@logger.debug(format("%sTry to parse %s", delimiter_repeat(@depth), grammar))
|
|
173
|
+
end
|
|
130
174
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
SyntaxToken.new(token.value, token.line_number)
|
|
134
|
-
end
|
|
175
|
+
key = parse_key
|
|
176
|
+
next unless key
|
|
135
177
|
|
|
136
|
-
|
|
137
|
-
|
|
178
|
+
name = nil
|
|
179
|
+
if check(Tokens::LiteralToken)
|
|
180
|
+
token = consume
|
|
181
|
+
name = Tree::SyntaxToken.new(token.value, token.line_number)
|
|
182
|
+
end
|
|
138
183
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
left_brace = LeftCurlyBrace.new(prefix: prefix, suffix: suffix)
|
|
184
|
+
prefix = consume_trivia
|
|
185
|
+
next nil unless check(Tokens::BlockStartToken)
|
|
142
186
|
|
|
143
|
-
|
|
187
|
+
advance
|
|
188
|
+
suffix = consume_trivia
|
|
189
|
+
left_brace = Tree::LeftCurlyBrace.new("{", nil, prefix, suffix)
|
|
144
190
|
|
|
145
|
-
|
|
146
|
-
return unless check(Tokens::BlockEndToken)
|
|
191
|
+
container = parse_container
|
|
147
192
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
193
|
+
prefix = consume_trivia
|
|
194
|
+
next nil unless check(Tokens::BlockEndToken)
|
|
195
|
+
|
|
196
|
+
advance
|
|
197
|
+
suffix = consume_trivia(only_newlines: true)
|
|
198
|
+
right_brace = Tree::RightCurlyBrace.new("}", nil, prefix, suffix)
|
|
199
|
+
|
|
200
|
+
blk = Tree::BlockNode.new(
|
|
201
|
+
type: key[0],
|
|
202
|
+
colon: key[1],
|
|
203
|
+
name: name,
|
|
204
|
+
left_brace: left_brace,
|
|
205
|
+
container: container,
|
|
206
|
+
right_brace: right_brace
|
|
207
|
+
)
|
|
208
|
+
@logger.debug("#{delimiter_repeat(@depth)}Successfully parsed block.") if @log_debug
|
|
209
|
+
blk
|
|
210
|
+
end
|
|
160
211
|
end
|
|
161
212
|
|
|
162
213
|
def parse_pair
|
|
163
|
-
|
|
164
|
-
|
|
214
|
+
backtrack_if_none do
|
|
215
|
+
if @log_debug
|
|
216
|
+
grammar = "[pair] = key value"
|
|
217
|
+
@logger.debug(format("%sTry to parse %s", delimiter_repeat(@depth), grammar))
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
key = parse_key
|
|
221
|
+
next unless key
|
|
165
222
|
|
|
166
|
-
|
|
167
|
-
|
|
223
|
+
value = parse_value(parse_prefix: true, parse_suffix: true)
|
|
224
|
+
next unless value
|
|
168
225
|
|
|
169
|
-
|
|
226
|
+
Tree::PairNode.new(type: key[0], value: value, colon: key[1])
|
|
227
|
+
end
|
|
170
228
|
end
|
|
171
229
|
|
|
172
230
|
def parse_key
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
231
|
+
backtrack_if_none do
|
|
232
|
+
if @log_debug
|
|
233
|
+
grammar = "[key] = literal ':'"
|
|
234
|
+
@logger.debug(format("%sTry to parse %s", delimiter_repeat(@depth), grammar))
|
|
235
|
+
end
|
|
178
236
|
|
|
179
|
-
|
|
237
|
+
prefix = consume_trivia
|
|
238
|
+
next nil unless check(Tokens::LiteralToken)
|
|
180
239
|
|
|
181
|
-
colon = nil
|
|
182
|
-
while check(Tokens::ValueToken)
|
|
183
240
|
token = consume
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return nil unless colon
|
|
241
|
+
key = Tree::SyntaxToken.new(token.value, token.line_number, prefix)
|
|
242
|
+
|
|
243
|
+
prefix = consume_trivia
|
|
188
244
|
|
|
189
|
-
|
|
245
|
+
colon = nil
|
|
246
|
+
while check(Tokens::ValueToken)
|
|
247
|
+
token = consume
|
|
248
|
+
suffix = consume_trivia
|
|
249
|
+
colon = Tree::Colon.new(":", token.line_number, prefix, suffix)
|
|
250
|
+
end
|
|
251
|
+
next unless colon
|
|
252
|
+
|
|
253
|
+
@logger.debug(format("%sSuccessfully parsed key.", delimiter_repeat(@depth))) if @log_debug
|
|
254
|
+
|
|
255
|
+
[key, colon]
|
|
256
|
+
end
|
|
190
257
|
end
|
|
191
258
|
|
|
192
|
-
def parse_value(parse_prefix: false, parse_suffix: false)
|
|
193
|
-
|
|
259
|
+
def parse_value(parse_prefix: false, parse_suffix: false)
|
|
260
|
+
backtrack_if_none do
|
|
261
|
+
if @log_debug
|
|
262
|
+
grammar = "[value] = literal / quoted_literal / expression_block"
|
|
263
|
+
@logger.debug(format("%sTry to parse %s", delimiter_repeat(@depth), grammar))
|
|
264
|
+
end
|
|
194
265
|
|
|
195
|
-
|
|
196
|
-
token = consume
|
|
197
|
-
if token.value == '-' && consume_trivia
|
|
198
|
-
return nil unless check(Tokens::LiteralToken)
|
|
266
|
+
prefix = parse_prefix ? consume_trivia : ""
|
|
199
267
|
|
|
268
|
+
if check(Tokens::LiteralToken)
|
|
200
269
|
token = consume
|
|
201
|
-
token.value
|
|
270
|
+
if token.value == "-" && !consume_trivia.empty?
|
|
271
|
+
next nil unless check(Tokens::LiteralToken)
|
|
202
272
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
273
|
+
token = consume
|
|
274
|
+
token.value = "-#{token.value}"
|
|
275
|
+
|
|
276
|
+
end
|
|
277
|
+
suffix = parse_suffix ? consume_trivia : ""
|
|
278
|
+
@logger.debug(format("%sSuccessfully parsed value.", delimiter_repeat(@depth))) if @log_debug
|
|
279
|
+
Tree::SyntaxToken.new(token.value, token.line_number, prefix, suffix)
|
|
280
|
+
elsif check(Tokens::QuotedLiteralToken)
|
|
281
|
+
token = consume
|
|
282
|
+
suffix = parse_suffix ? consume_trivia : ""
|
|
283
|
+
@logger.debug(format("%sSuccessfully parsed value.", delimiter_repeat(@depth))) if @log_debug
|
|
284
|
+
Tree::QuotedSyntaxToken.new(token.value, token.line_number, prefix, suffix)
|
|
285
|
+
elsif check(Tokens::ExpressionBlockToken)
|
|
286
|
+
token = consume
|
|
287
|
+
expr_prefix, value, expr_suffix = Utils.strip(token.value)
|
|
288
|
+
prefix += expr_prefix
|
|
219
289
|
|
|
220
|
-
|
|
290
|
+
next nil unless check(Tokens::ExpressionBlockEndToken)
|
|
221
291
|
|
|
222
|
-
|
|
292
|
+
advance
|
|
223
293
|
|
|
224
|
-
|
|
225
|
-
|
|
294
|
+
suffix = parse_suffix ? consume_trivia : ""
|
|
295
|
+
@logger.debug(format("%sSuccessfully parsed value.", delimiter_repeat(@depth))) if @log_debug
|
|
296
|
+
Tree::ExpressionSyntaxToken.new(value, token.line_number, prefix, suffix, expr_suffix)
|
|
297
|
+
end
|
|
226
298
|
end
|
|
227
299
|
end
|
|
228
300
|
|
|
229
301
|
def parse_list
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
302
|
+
backtrack_if_none do
|
|
303
|
+
if @log_debug
|
|
304
|
+
grammar = "[list] = key '[' csv? ']'"
|
|
305
|
+
@logger.debug(format("%sTry to parse %s", delimiter_repeat(@depth), grammar))
|
|
306
|
+
end
|
|
235
307
|
|
|
236
|
-
|
|
237
|
-
|
|
308
|
+
key = parse_key
|
|
309
|
+
next unless key
|
|
238
310
|
|
|
239
|
-
|
|
311
|
+
prefix = consume_trivia
|
|
312
|
+
next nil unless check(Tokens::ListStartToken)
|
|
240
313
|
|
|
241
|
-
|
|
314
|
+
advance
|
|
315
|
+
left_bracket = Tree::LeftBracket.new("[", nil, prefix, "")
|
|
242
316
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
suffix = consume_trivia
|
|
246
|
-
right_bracket = RightBracket.new(prefix: prefix, suffix: suffix)
|
|
247
|
-
ListNode.new(
|
|
248
|
-
key[0],
|
|
249
|
-
items: csv.values,
|
|
250
|
-
left_bracket: left_bracket,
|
|
251
|
-
right_bracket: right_bracket,
|
|
252
|
-
colon: key[1],
|
|
253
|
-
leading_comma: csv.leading_comma,
|
|
254
|
-
trailing_comma: csv.trailing_comma
|
|
255
|
-
)
|
|
256
|
-
end
|
|
317
|
+
csv = parse_csv
|
|
318
|
+
csv ||= CommaSeparatedValues.new
|
|
257
319
|
|
|
258
|
-
|
|
259
|
-
pair_mode = false
|
|
260
|
-
csv = CommaSeparatedValues.new
|
|
261
|
-
csv.leading_comma = parse_comma
|
|
320
|
+
next nil unless check(Tokens::ListEndToken, skip_trivia: true)
|
|
262
321
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
322
|
+
prefix = consume_trivia
|
|
323
|
+
advance
|
|
324
|
+
suffix = consume_trivia
|
|
325
|
+
right_bracket = Tree::RightBracket.new("]", nil, prefix, suffix)
|
|
326
|
+
|
|
327
|
+
Tree::ListNode.new(
|
|
328
|
+
type: key[0],
|
|
329
|
+
colon: key[1],
|
|
330
|
+
left_bracket: left_bracket,
|
|
331
|
+
items: csv.frozen_tuple,
|
|
332
|
+
right_bracket: right_bracket,
|
|
333
|
+
leading_comma: csv.leading_comma,
|
|
334
|
+
trailing_comma: csv.trailing_comma
|
|
335
|
+
)
|
|
272
336
|
end
|
|
337
|
+
end
|
|
273
338
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
if check(Tokens::ListEndToken, skip_trivia: true)
|
|
280
|
-
jump_to_index(index)
|
|
281
|
-
csv.trailing_comma = parse_comma
|
|
282
|
-
break
|
|
339
|
+
def parse_csv
|
|
340
|
+
backtrack_if_none do
|
|
341
|
+
if @log_debug
|
|
342
|
+
grammar = "[csv] = \",\"? (literal / quoted_literal) (\",\" (literal / quoted_literal))* \",\"?"
|
|
343
|
+
@logger.debug(format("%sTry to parse %s", delimiter_repeat(@depth), grammar))
|
|
283
344
|
end
|
|
284
345
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
346
|
+
pair_mode = false
|
|
347
|
+
csv = CommaSeparatedValues.new
|
|
348
|
+
csv.leading_comma = parse_comma
|
|
288
349
|
|
|
350
|
+
pair = parse_pair
|
|
351
|
+
if pair
|
|
289
352
|
csv.append(pair)
|
|
353
|
+
pair_mode = true
|
|
290
354
|
elsif check(Tokens::LiteralToken, Tokens::QuotedLiteralToken, skip_trivia: true)
|
|
291
355
|
value = parse_value(parse_prefix: true, parse_suffix: true)
|
|
292
356
|
csv.append(value)
|
|
293
|
-
elsif check(Tokens::ListEndToken, skip_trivia: true)
|
|
294
|
-
break
|
|
295
357
|
else
|
|
296
|
-
|
|
358
|
+
next nil
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# `next` inside `until` continues the until-loop (Ruby), not the outer backtrack block — use break + flag.
|
|
362
|
+
csv_abort = false
|
|
363
|
+
until check(Tokens::ListEndToken, skip_trivia: true)
|
|
364
|
+
unless check(Tokens::CommaToken)
|
|
365
|
+
csv_abort = true
|
|
366
|
+
break
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
comma_index = @index
|
|
370
|
+
advance
|
|
371
|
+
if check(Tokens::ListEndToken, skip_trivia: true)
|
|
372
|
+
jump_to_index(comma_index)
|
|
373
|
+
csv.trailing_comma = parse_comma
|
|
374
|
+
break
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
if pair_mode
|
|
378
|
+
pair = parse_pair
|
|
379
|
+
unless pair
|
|
380
|
+
csv_abort = true
|
|
381
|
+
break
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
csv.append(pair)
|
|
385
|
+
elsif check(Tokens::LiteralToken, Tokens::QuotedLiteralToken, skip_trivia: true)
|
|
386
|
+
value = parse_value(parse_prefix: true, parse_suffix: true)
|
|
387
|
+
csv.append(value)
|
|
388
|
+
elsif check(Tokens::ListEndToken, skip_trivia: true)
|
|
389
|
+
break
|
|
390
|
+
else
|
|
391
|
+
csv_abort = true
|
|
392
|
+
break
|
|
393
|
+
end
|
|
297
394
|
end
|
|
298
|
-
end
|
|
299
395
|
|
|
300
|
-
|
|
396
|
+
next nil if csv_abort
|
|
397
|
+
|
|
398
|
+
@logger.debug(format("%sSuccessfully parsed comma-separated values.", delimiter_repeat(@depth))) if @log_debug
|
|
399
|
+
csv
|
|
400
|
+
end
|
|
301
401
|
end
|
|
302
402
|
|
|
303
403
|
def parse_comma
|
|
304
|
-
|
|
404
|
+
backtrack_if_none do
|
|
405
|
+
prefix = consume_trivia
|
|
406
|
+
next nil unless check(Tokens::CommaToken)
|
|
407
|
+
|
|
408
|
+
advance
|
|
409
|
+
suffix = if check(Tokens::ListEndToken, skip_trivia: true)
|
|
410
|
+
""
|
|
411
|
+
else
|
|
412
|
+
consume_trivia
|
|
413
|
+
end
|
|
414
|
+
Tree::Comma.new(",", nil, prefix, suffix)
|
|
415
|
+
end
|
|
416
|
+
end
|
|
305
417
|
|
|
306
|
-
|
|
418
|
+
private
|
|
307
419
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
420
|
+
def delimiter_repeat(count)
|
|
421
|
+
n = Integer(count)
|
|
422
|
+
return +"" if n <= 0
|
|
423
|
+
|
|
424
|
+
DELIMITER * n
|
|
311
425
|
end
|
|
312
426
|
|
|
313
|
-
def
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if result.nil?
|
|
322
|
-
@progress = [@index, @progress].max
|
|
323
|
-
jump_to_index(mark)
|
|
427
|
+
def backtrack_if_none
|
|
428
|
+
mark = @index
|
|
429
|
+
@depth += 1
|
|
430
|
+
result =
|
|
431
|
+
begin
|
|
432
|
+
yield
|
|
433
|
+
ensure
|
|
434
|
+
@depth -= 1
|
|
324
435
|
end
|
|
325
|
-
|
|
436
|
+
if result.nil?
|
|
437
|
+
@progress = [@index, @progress].max
|
|
438
|
+
jump_to_index(mark)
|
|
326
439
|
end
|
|
440
|
+
result
|
|
327
441
|
end
|
|
328
|
-
|
|
329
|
-
backtrack_if_none :parse_container
|
|
330
|
-
backtrack_if_none :parse_block
|
|
331
|
-
backtrack_if_none :parse_pair
|
|
332
|
-
backtrack_if_none :parse_key
|
|
333
|
-
backtrack_if_none :parse_value
|
|
334
|
-
backtrack_if_none :parse_list
|
|
335
|
-
backtrack_if_none :parse_csv
|
|
336
|
-
backtrack_if_none :parse_comma
|
|
337
442
|
end
|
|
338
443
|
end
|