rjq 0.1.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/ARCHITECTURE.md +38 -0
- data/CHANGELOG.md +7 -0
- data/COMPATIBILITY.md +48 -0
- data/CONTRIBUTING.md +30 -0
- data/LICENSE.txt +21 -0
- data/README.md +192 -0
- data/SECURITY.md +10 -0
- data/bin/rjq +9 -0
- data/lib/rjq/ast.rb +1475 -0
- data/lib/rjq/builtins/array.rb +3 -0
- data/lib/rjq/builtins/core.rb +3 -0
- data/lib/rjq/builtins/date.rb +3 -0
- data/lib/rjq/builtins/format.rb +3 -0
- data/lib/rjq/builtins/io.rb +3 -0
- data/lib/rjq/builtins/math.rb +3 -0
- data/lib/rjq/builtins/regex.rb +3 -0
- data/lib/rjq/builtins/sql.rb +3 -0
- data/lib/rjq/builtins/stream.rb +3 -0
- data/lib/rjq/builtins/string.rb +3 -0
- data/lib/rjq/builtins.rb +2103 -0
- data/lib/rjq/cli.rb +459 -0
- data/lib/rjq/color.rb +36 -0
- data/lib/rjq/compiler.rb +392 -0
- data/lib/rjq/errors.rb +76 -0
- data/lib/rjq/json/dumper.rb +191 -0
- data/lib/rjq/json/input_buffer.rb +99 -0
- data/lib/rjq/json/parser.rb +405 -0
- data/lib/rjq/json/stream_parser.rb +526 -0
- data/lib/rjq/json.rb +4 -0
- data/lib/rjq/lexer.rb +344 -0
- data/lib/rjq/math_functions.rb +168 -0
- data/lib/rjq/module_loader.rb +178 -0
- data/lib/rjq/modules.rb +88 -0
- data/lib/rjq/number.rb +189 -0
- data/lib/rjq/opcodes.rb +130 -0
- data/lib/rjq/parser.rb +755 -0
- data/lib/rjq/path.rb +250 -0
- data/lib/rjq/runtime.rb +377 -0
- data/lib/rjq/semantic_analyzer.rb +209 -0
- data/lib/rjq/value.rb +287 -0
- data/lib/rjq/version.rb +5 -0
- data/lib/rjq/vm.rb +1359 -0
- data/lib/rjq.rb +41 -0
- metadata +106 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rjq
|
|
4
|
+
module JSON
|
|
5
|
+
class StreamParser
|
|
6
|
+
DEFAULT_MAX_DEPTH = 256
|
|
7
|
+
Result = Struct.new(:events, :close_path, keyword_init: true)
|
|
8
|
+
ParsedEvent = Struct.new(:value, :line, keyword_init: true)
|
|
9
|
+
class StreamError < StandardError
|
|
10
|
+
attr_reader :path
|
|
11
|
+
|
|
12
|
+
def initialize(message:, path:)
|
|
13
|
+
@path = path
|
|
14
|
+
super(message)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
def parse(io_or_string, seq: false, stream_errors: false, chunk_size: InputBuffer::DEFAULT_CHUNK_SIZE,
|
|
20
|
+
on_error: nil, max_depth: DEFAULT_MAX_DEPTH, max_number_digits: nil, max_string_bytes: nil,
|
|
21
|
+
locations: false)
|
|
22
|
+
new(io_or_string, seq: seq, stream_errors: stream_errors, chunk_size: chunk_size,
|
|
23
|
+
on_error: on_error, max_depth: max_depth, max_number_digits: max_number_digits,
|
|
24
|
+
max_string_bytes: max_string_bytes, locations: locations).parse
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def initialize(input, seq: false, stream_errors: false, chunk_size: InputBuffer::DEFAULT_CHUNK_SIZE,
|
|
29
|
+
on_error: nil, max_depth: DEFAULT_MAX_DEPTH, max_number_digits: nil, max_string_bytes: nil,
|
|
30
|
+
locations: false)
|
|
31
|
+
validate_options!(chunk_size, max_depth, max_number_digits, max_string_bytes)
|
|
32
|
+
@input = InputBuffer.new(input, chunk_size: chunk_size)
|
|
33
|
+
@seq = seq
|
|
34
|
+
@stream_errors = stream_errors
|
|
35
|
+
@on_error = on_error
|
|
36
|
+
@max_depth = max_depth
|
|
37
|
+
@max_number_digits = max_number_digits
|
|
38
|
+
@max_string_bytes = max_string_bytes
|
|
39
|
+
@locations = locations
|
|
40
|
+
@depth = 0
|
|
41
|
+
@index = 0
|
|
42
|
+
@line = 1
|
|
43
|
+
@column = 1
|
|
44
|
+
advance if current == "\uFEFF"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def parse
|
|
48
|
+
Enumerator.new do |yielder|
|
|
49
|
+
@events = yielder
|
|
50
|
+
parse_values
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def parse_values
|
|
57
|
+
until eof?
|
|
58
|
+
skip_separators
|
|
59
|
+
break if eof?
|
|
60
|
+
|
|
61
|
+
begin
|
|
62
|
+
value_line = @line
|
|
63
|
+
value_column = @column
|
|
64
|
+
result = parse_value([])
|
|
65
|
+
skip_whitespace
|
|
66
|
+
commit(result)
|
|
67
|
+
@unmatched_error_location = [value_line, value_column] if @unmatched_error_location
|
|
68
|
+
raise_error('expected record separator', []) if @seq && !eof? && current != "\x1e"
|
|
69
|
+
@input.discard_before(@index)
|
|
70
|
+
rescue StreamError => e
|
|
71
|
+
if @stream_errors
|
|
72
|
+
emit([e.message, e.path])
|
|
73
|
+
elsif @seq && @on_error
|
|
74
|
+
@on_error.call(e.message)
|
|
75
|
+
else
|
|
76
|
+
raise JSONParseError, e.message
|
|
77
|
+
end
|
|
78
|
+
@unmatched_error_location = [@last_error_line, @last_error_column + 1]
|
|
79
|
+
if @seq
|
|
80
|
+
resync_to_record_separator
|
|
81
|
+
elsif @stream_errors
|
|
82
|
+
resync_to_next_line
|
|
83
|
+
else
|
|
84
|
+
break
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
@events
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def parse_value(path)
|
|
92
|
+
skip_whitespace
|
|
93
|
+
raise_error('Unfinished JSON term at EOF', path) if eof?
|
|
94
|
+
|
|
95
|
+
case current
|
|
96
|
+
when '{'
|
|
97
|
+
parse_object(path)
|
|
98
|
+
when '['
|
|
99
|
+
parse_array(path)
|
|
100
|
+
when '"'
|
|
101
|
+
Result.new(events: [pending_event(path, parse_string(path))], close_path: path)
|
|
102
|
+
when 't'
|
|
103
|
+
consume_literal('true', path)
|
|
104
|
+
Result.new(events: [pending_event(path, true)], close_path: path)
|
|
105
|
+
when 'f'
|
|
106
|
+
consume_literal('false', path)
|
|
107
|
+
Result.new(events: [pending_event(path, false)], close_path: path)
|
|
108
|
+
when 'n'
|
|
109
|
+
consume_literal('null', path)
|
|
110
|
+
Result.new(events: [pending_event(path, nil)], close_path: path)
|
|
111
|
+
when ']', '}'
|
|
112
|
+
delimiter = current
|
|
113
|
+
if @unmatched_error_location
|
|
114
|
+
line, column = @unmatched_error_location
|
|
115
|
+
@unmatched_error_location = nil
|
|
116
|
+
raise_error_at("Unmatched '#{delimiter}' at the top-level", path, line, column)
|
|
117
|
+
end
|
|
118
|
+
raise_error("Unmatched '#{delimiter}' at the top-level", path)
|
|
119
|
+
else
|
|
120
|
+
Result.new(events: [pending_event(path, parse_number(path))], close_path: path)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def parse_object(path)
|
|
125
|
+
with_container_depth(path) { parse_object_body(path) }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def parse_object_body(path)
|
|
129
|
+
advance
|
|
130
|
+
skip_whitespace
|
|
131
|
+
if consume?('}')
|
|
132
|
+
emit([path, {}])
|
|
133
|
+
return Result.new(events: [], close_path: path)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
last_path = path
|
|
137
|
+
loop do
|
|
138
|
+
skip_whitespace
|
|
139
|
+
raise_error('Unfinished JSON term at EOF', path + [nil]) if eof?
|
|
140
|
+
raise_error('Expected another key:value pair', path + [nil]) if current == '}'
|
|
141
|
+
unless current == '"'
|
|
142
|
+
advance while current&.match?(/[0-9A-Za-z_+-]/)
|
|
143
|
+
raise_error('Invalid numeric literal', path + [nil])
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
key = parse_string(path + [nil])
|
|
147
|
+
skip_whitespace
|
|
148
|
+
unless consume?(':')
|
|
149
|
+
raise_error('Unfinished JSON term at EOF', path + [nil]) if eof?
|
|
150
|
+
|
|
151
|
+
scan_unexpected_value
|
|
152
|
+
raise_error('Expected separator between values', path + [nil])
|
|
153
|
+
end
|
|
154
|
+
skip_whitespace
|
|
155
|
+
raise_error('Missing value in key:value pair', path + [key]) if ['}', ']'].include?(current)
|
|
156
|
+
|
|
157
|
+
result = parse_value(path + [key])
|
|
158
|
+
skip_whitespace
|
|
159
|
+
|
|
160
|
+
if consume?(',')
|
|
161
|
+
commit(result)
|
|
162
|
+
last_path = result.close_path
|
|
163
|
+
next
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
if consume?('}')
|
|
167
|
+
commit(result)
|
|
168
|
+
last_path = result.close_path
|
|
169
|
+
emit([last_path])
|
|
170
|
+
return Result.new(events: [], close_path: path)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
raise_error('Unfinished JSON term at EOF', result.close_path) if eof?
|
|
174
|
+
|
|
175
|
+
scan_unexpected_value
|
|
176
|
+
raise_error('Expected separator between values', result.close_path)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def parse_array(path)
|
|
181
|
+
with_container_depth(path) { parse_array_body(path) }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def parse_array_body(path)
|
|
185
|
+
advance
|
|
186
|
+
skip_whitespace
|
|
187
|
+
if consume?(']')
|
|
188
|
+
emit([path, []])
|
|
189
|
+
return Result.new(events: [], close_path: path)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
index = 0
|
|
193
|
+
last_path = path
|
|
194
|
+
loop do
|
|
195
|
+
skip_whitespace
|
|
196
|
+
raise_error('Expected another array element', path + [index]) if current == ']'
|
|
197
|
+
|
|
198
|
+
result = parse_value(path + [index])
|
|
199
|
+
skip_whitespace
|
|
200
|
+
|
|
201
|
+
if consume?(',')
|
|
202
|
+
commit(result)
|
|
203
|
+
last_path = result.close_path
|
|
204
|
+
index += 1
|
|
205
|
+
next
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
if consume?(']')
|
|
209
|
+
commit(result)
|
|
210
|
+
last_path = result.close_path
|
|
211
|
+
emit([last_path])
|
|
212
|
+
return Result.new(events: [], close_path: path)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
raise_error('Unfinished JSON term at EOF', result.close_path) if eof?
|
|
216
|
+
|
|
217
|
+
scan_unexpected_value
|
|
218
|
+
raise_error('Expected separator between values', result.close_path)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def commit(result)
|
|
223
|
+
result.events.each { |event| emit(event) }
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def emit(event)
|
|
227
|
+
parsed = event.is_a?(ParsedEvent) ? event : located_event(event)
|
|
228
|
+
@events << (@locations ? parsed : parsed.value)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def located_event(value)
|
|
232
|
+
ParsedEvent.new(value: value, line: @line)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def pending_event(path, value)
|
|
236
|
+
event = [path, value]
|
|
237
|
+
path.empty? ? located_event(event) : event
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def parse_string(path = [])
|
|
241
|
+
expect('"', [])
|
|
242
|
+
out = +''
|
|
243
|
+
bytes = 0
|
|
244
|
+
until eof?
|
|
245
|
+
line = @line
|
|
246
|
+
column = @column
|
|
247
|
+
char = advance
|
|
248
|
+
return out.force_encoding(Encoding::UTF_8) if char == '"'
|
|
249
|
+
|
|
250
|
+
piece = if char == '\\'
|
|
251
|
+
parse_escape
|
|
252
|
+
else
|
|
253
|
+
raise_error('unescaped control character in string', path) if char.ord < 0x20
|
|
254
|
+
|
|
255
|
+
char
|
|
256
|
+
end
|
|
257
|
+
bytes += piece.bytesize
|
|
258
|
+
if @max_string_bytes && bytes > @max_string_bytes
|
|
259
|
+
raise_error_at("string exceeds #{@max_string_bytes} byte limit", path, line, column)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
out << piece
|
|
263
|
+
end
|
|
264
|
+
raise_error('Unfinished JSON term at EOF', [])
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def parse_escape
|
|
268
|
+
raise_error('Unfinished JSON term at EOF', []) if eof?
|
|
269
|
+
|
|
270
|
+
char = advance
|
|
271
|
+
case char
|
|
272
|
+
when '"', '\\', '/'
|
|
273
|
+
char
|
|
274
|
+
when 'b'
|
|
275
|
+
"\b"
|
|
276
|
+
when 'f'
|
|
277
|
+
"\f"
|
|
278
|
+
when 'n'
|
|
279
|
+
"\n"
|
|
280
|
+
when 'r'
|
|
281
|
+
"\r"
|
|
282
|
+
when 't'
|
|
283
|
+
"\t"
|
|
284
|
+
when 'u'
|
|
285
|
+
parse_unicode_escape
|
|
286
|
+
else
|
|
287
|
+
raise_error("invalid escape: \\#{char}", [])
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def parse_unicode_escape
|
|
292
|
+
codepoint = read_hex4
|
|
293
|
+
if high_surrogate?(codepoint)
|
|
294
|
+
raise_error('missing low surrogate', []) unless @input[@index, 2] == '\\u'
|
|
295
|
+
|
|
296
|
+
2.times { advance }
|
|
297
|
+
low = read_hex4
|
|
298
|
+
raise_error('invalid low surrogate', []) unless low_surrogate?(low)
|
|
299
|
+
|
|
300
|
+
codepoint = 0x10000 + ((codepoint - 0xD800) << 10) + (low - 0xDC00)
|
|
301
|
+
elsif low_surrogate?(codepoint)
|
|
302
|
+
raise_error('unexpected low surrogate', [])
|
|
303
|
+
end
|
|
304
|
+
[codepoint].pack('U')
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
def parse_number(path)
|
|
308
|
+
start = @index
|
|
309
|
+
digits = 0
|
|
310
|
+
consume?('-')
|
|
311
|
+
if current == '0'
|
|
312
|
+
digits = consume_number_digit(digits, path)
|
|
313
|
+
invalid_numeric_literal(path) if digit?(current)
|
|
314
|
+
else
|
|
315
|
+
invalid_numeric_literal(path) unless digit_1_9?(current)
|
|
316
|
+
digits = consume_number_digit(digits, path) while digit?(current)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
if consume?('.')
|
|
320
|
+
invalid_numeric_literal(path) unless digit?(current)
|
|
321
|
+
digits = consume_number_digit(digits, path) while digit?(current)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
if %w[e E].include?(current)
|
|
325
|
+
advance
|
|
326
|
+
advance if ['+', '-'].include?(current)
|
|
327
|
+
invalid_numeric_literal(path) unless digit?(current)
|
|
328
|
+
digits = consume_number_digit(digits, path) while digit?(current)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
literal = @input[start...@index]
|
|
332
|
+
invalid_numeric_literal(path) unless number_delimiter?(current)
|
|
333
|
+
|
|
334
|
+
Number.parse(literal)
|
|
335
|
+
rescue ArgumentError
|
|
336
|
+
invalid_numeric_literal(path)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def number_delimiter?(char)
|
|
340
|
+
char.nil? || char.match?(/[\s,\]}\x1e]/)
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def consume_literal(literal, path)
|
|
344
|
+
unless @input[@index, literal.length] == literal
|
|
345
|
+
scan_invalid_token
|
|
346
|
+
raise_error(eof? ? 'Invalid literal at EOF' : 'Invalid literal', path)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
literal.length.times { advance }
|
|
350
|
+
if atom_char?(current)
|
|
351
|
+
scan_invalid_token
|
|
352
|
+
raise_error(eof? ? 'Invalid literal at EOF' : 'Invalid literal', path)
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def atom_char?(char)
|
|
357
|
+
char&.match?(/[0-9A-Za-z_]/)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def read_hex4
|
|
361
|
+
chars = @input[@index, 4]
|
|
362
|
+
raise_error('invalid unicode escape', []) unless chars&.match?(/\A[0-9a-fA-F]{4}\z/)
|
|
363
|
+
|
|
364
|
+
4.times { advance }
|
|
365
|
+
chars.to_i(16)
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def high_surrogate?(codepoint)
|
|
369
|
+
codepoint.between?(0xD800, 0xDBFF)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def low_surrogate?(codepoint)
|
|
373
|
+
codepoint.between?(0xDC00, 0xDFFF)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
def skip_separators
|
|
377
|
+
loop do
|
|
378
|
+
skip_whitespace
|
|
379
|
+
break unless @seq && current == "\x1e"
|
|
380
|
+
|
|
381
|
+
advance
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def resync_to_record_separator
|
|
386
|
+
advance until eof? || current == "\x1e"
|
|
387
|
+
@input.discard_before(@index)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def resync_to_next_line
|
|
391
|
+
advance until eof? || current == "\n"
|
|
392
|
+
advance if current == "\n"
|
|
393
|
+
@input.discard_before(@index)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def with_container_depth(path)
|
|
397
|
+
@depth += 1
|
|
398
|
+
raise_error('Exceeds depth limit for parsing', path) if @depth > @max_depth
|
|
399
|
+
|
|
400
|
+
yield
|
|
401
|
+
ensure
|
|
402
|
+
@depth -= 1
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
def consume_number_digit(count, path)
|
|
406
|
+
count += 1
|
|
407
|
+
if @max_number_digits && count > @max_number_digits
|
|
408
|
+
raise_error("Number exceeds #{@max_number_digits} digit limit", path)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
advance
|
|
412
|
+
count
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
def validate_options!(chunk_size, max_depth, max_number_digits, max_string_bytes)
|
|
416
|
+
validate_nonnegative_integer!(:max_depth, max_depth)
|
|
417
|
+
validate_nonnegative_integer!(:max_number_digits, max_number_digits, optional: true)
|
|
418
|
+
validate_nonnegative_integer!(:max_string_bytes, max_string_bytes, optional: true)
|
|
419
|
+
return if chunk_size.is_a?(Integer) && chunk_size.positive?
|
|
420
|
+
|
|
421
|
+
raise ArgumentError, 'chunk_size must be a positive Integer'
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
def validate_nonnegative_integer!(name, value, optional: false)
|
|
425
|
+
return if optional && value.nil?
|
|
426
|
+
return if value.is_a?(Integer) && value >= 0
|
|
427
|
+
|
|
428
|
+
raise ArgumentError, "#{name} must be a non-negative Integer#{' or nil' if optional}"
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def skip_whitespace
|
|
432
|
+
advance while current&.match?(/[ \t\r\n]/)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def scan_unexpected_value
|
|
436
|
+
skip_whitespace
|
|
437
|
+
return if eof? || current == ',' || current == ']' || current == '}'
|
|
438
|
+
|
|
439
|
+
if current == '"'
|
|
440
|
+
begin
|
|
441
|
+
parse_string
|
|
442
|
+
rewind_one unless @index.zero?
|
|
443
|
+
rescue StreamError
|
|
444
|
+
nil
|
|
445
|
+
end
|
|
446
|
+
else
|
|
447
|
+
scan_invalid_token
|
|
448
|
+
end
|
|
449
|
+
skip_whitespace
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def scan_invalid_token
|
|
453
|
+
advance while current && !current.match?(/[,\]\s}\x1e]/)
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def invalid_numeric_literal(path)
|
|
457
|
+
scan_invalid_token
|
|
458
|
+
raise_error(eof? ? 'Invalid numeric literal at EOF' : 'Invalid numeric literal', path)
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
def expect(char, path)
|
|
462
|
+
raise_error("expected #{char}", path) unless consume?(char)
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
def consume?(char)
|
|
466
|
+
return false unless current == char
|
|
467
|
+
|
|
468
|
+
advance
|
|
469
|
+
true
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def advance
|
|
473
|
+
char = current
|
|
474
|
+
@index += char.length
|
|
475
|
+
if char == "\n"
|
|
476
|
+
@line += 1
|
|
477
|
+
@column = 1
|
|
478
|
+
else
|
|
479
|
+
@column += 1
|
|
480
|
+
end
|
|
481
|
+
char
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
def rewind_one
|
|
485
|
+
@index -= 1
|
|
486
|
+
@column -= 1
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def current
|
|
490
|
+
@input[@index]
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def eof?
|
|
494
|
+
current.nil?
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def digit?(char)
|
|
498
|
+
!char.nil? && char >= '0' && char <= '9'
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
def digit_1_9?(char)
|
|
502
|
+
!char.nil? && char >= '1' && char <= '9'
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def raise_error(message, path)
|
|
506
|
+
@last_error_line = line_number
|
|
507
|
+
@last_error_column = column_number
|
|
508
|
+
raise StreamError.new(message: "#{message} at line #{line_number}, column #{column_number}", path: path)
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
def raise_error_at(message, path, line, column)
|
|
512
|
+
@last_error_line = line
|
|
513
|
+
@last_error_column = column
|
|
514
|
+
raise StreamError.new(message: "#{message} at line #{line}, column #{column}", path: path)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def line_number
|
|
518
|
+
@line
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def column_number
|
|
522
|
+
eof? ? [@column - 1, 1].max : @column
|
|
523
|
+
end
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
end
|
data/lib/rjq/json.rb
ADDED