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,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rjq
|
|
4
|
+
module JSON
|
|
5
|
+
class InputBuffer
|
|
6
|
+
DEFAULT_CHUNK_SIZE = 16_384
|
|
7
|
+
|
|
8
|
+
def initialize(io_or_string, chunk_size: DEFAULT_CHUNK_SIZE)
|
|
9
|
+
@io = io_or_string if io_or_string.respond_to?(:read)
|
|
10
|
+
@chunk_size = chunk_size
|
|
11
|
+
@offset = 0
|
|
12
|
+
@eof = !@io
|
|
13
|
+
@buffer = @io ? +''.b : io_or_string.to_s.dup
|
|
14
|
+
normalize_encoding!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def [](index, length = nil)
|
|
18
|
+
if index.is_a?(Range)
|
|
19
|
+
range_end = index.end
|
|
20
|
+
range_end -= 1 if index.exclude_end?
|
|
21
|
+
ensure_index(range_end)
|
|
22
|
+
return @buffer[local_index(index.begin)..local_index(index.end)] unless index.exclude_end?
|
|
23
|
+
|
|
24
|
+
return @buffer[local_index(index.begin)...local_index(index.end)]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
ensure_index(index + length - 1) if length
|
|
28
|
+
ensure_index(index) unless length
|
|
29
|
+
length ? @buffer[local_index(index), length] : @buffer[local_index(index)]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def discard_before(index)
|
|
33
|
+
return if index <= @offset
|
|
34
|
+
|
|
35
|
+
local = [index - @offset, @buffer.length].min
|
|
36
|
+
@buffer = @buffer[local..].to_s
|
|
37
|
+
@offset += local
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def ensure_index(index)
|
|
43
|
+
return if index.negative?
|
|
44
|
+
|
|
45
|
+
read_more while !@eof && index >= @offset + @buffer.length
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def read_more
|
|
49
|
+
loop do
|
|
50
|
+
chunk = @io.read(@chunk_size)
|
|
51
|
+
if chunk.nil? || chunk.empty?
|
|
52
|
+
@eof = true
|
|
53
|
+
validate_encoding!
|
|
54
|
+
return
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
@buffer = @buffer.b << chunk.b
|
|
58
|
+
@buffer.force_encoding(Encoding::UTF_8)
|
|
59
|
+
return if @buffer.valid_encoding?
|
|
60
|
+
|
|
61
|
+
validate_encoding! unless incomplete_utf8_suffix?
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def normalize_encoding!
|
|
66
|
+
@buffer = @buffer.encode(Encoding::UTF_8)
|
|
67
|
+
validate_encoding!
|
|
68
|
+
rescue EncodingError => e
|
|
69
|
+
raise JSONParseError, e.message
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def validate_encoding!
|
|
73
|
+
raise JSONParseError, 'invalid UTF-8 input' unless @buffer.valid_encoding?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def incomplete_utf8_suffix?
|
|
77
|
+
bytes = @buffer.bytes
|
|
78
|
+
start = [bytes.length - 3, 0].max
|
|
79
|
+
(start...bytes.length).any? do |index|
|
|
80
|
+
lead = bytes[index]
|
|
81
|
+
expected = case lead
|
|
82
|
+
when 0xC2..0xDF then 2
|
|
83
|
+
when 0xE0..0xEF then 3
|
|
84
|
+
when 0xF0..0xF4 then 4
|
|
85
|
+
end
|
|
86
|
+
next false unless expected
|
|
87
|
+
|
|
88
|
+
suffix = bytes[index..]
|
|
89
|
+
suffix.length < expected && suffix.drop(1).all? { |byte| byte.between?(0x80, 0xBF) } &&
|
|
90
|
+
bytes[0...index].pack('C*').force_encoding(Encoding::UTF_8).valid_encoding?
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def local_index(index)
|
|
95
|
+
index - @offset
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rjq
|
|
4
|
+
module JSON
|
|
5
|
+
class Parser
|
|
6
|
+
ParsedValue = Struct.new(:value, :line, keyword_init: true)
|
|
7
|
+
DEFAULT_MAX_DEPTH = 256
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def parse(io_or_string, seq: false, chunk_size: InputBuffer::DEFAULT_CHUNK_SIZE, on_error: nil,
|
|
11
|
+
max_depth: DEFAULT_MAX_DEPTH, max_number_digits: nil, max_string_bytes: nil)
|
|
12
|
+
new(io_or_string, seq: seq, chunk_size: chunk_size, on_error: on_error,
|
|
13
|
+
max_depth: max_depth, max_number_digits: max_number_digits,
|
|
14
|
+
max_string_bytes: max_string_bytes).parse_stream
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parse_one(io_or_string, seq: false, chunk_size: InputBuffer::DEFAULT_CHUNK_SIZE,
|
|
18
|
+
max_depth: DEFAULT_MAX_DEPTH, max_number_digits: nil, max_string_bytes: nil)
|
|
19
|
+
values = parse(io_or_string, seq: seq, chunk_size: chunk_size, max_depth: max_depth,
|
|
20
|
+
max_number_digits: max_number_digits, max_string_bytes: max_string_bytes).take(2)
|
|
21
|
+
raise JSONParseError, "expected one JSON value, got #{values.length}" unless values.length == 1
|
|
22
|
+
|
|
23
|
+
values.first
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def parse_records(io_or_string, seq: false, chunk_size: InputBuffer::DEFAULT_CHUNK_SIZE, on_error: nil,
|
|
27
|
+
max_depth: DEFAULT_MAX_DEPTH, max_number_digits: nil, max_string_bytes: nil)
|
|
28
|
+
new(io_or_string, seq: seq, chunk_size: chunk_size, on_error: on_error,
|
|
29
|
+
max_depth: max_depth, max_number_digits: max_number_digits,
|
|
30
|
+
max_string_bytes: max_string_bytes).parse_stream(locations: true)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def initialize(input, seq: false, chunk_size: InputBuffer::DEFAULT_CHUNK_SIZE, on_error: nil,
|
|
35
|
+
max_depth: DEFAULT_MAX_DEPTH, max_number_digits: nil, max_string_bytes: nil)
|
|
36
|
+
validate_options!(chunk_size, max_depth, max_number_digits, max_string_bytes)
|
|
37
|
+
@input = InputBuffer.new(input, chunk_size: chunk_size)
|
|
38
|
+
@seq = seq
|
|
39
|
+
@on_error = on_error
|
|
40
|
+
@max_depth = max_depth
|
|
41
|
+
@max_number_digits = max_number_digits
|
|
42
|
+
@max_string_bytes = max_string_bytes
|
|
43
|
+
@depth = 0
|
|
44
|
+
@index = 0
|
|
45
|
+
@line = 1
|
|
46
|
+
@column = 1
|
|
47
|
+
advance if current == "\uFEFF"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parse_stream(locations: false)
|
|
51
|
+
Enumerator.new do |yielder|
|
|
52
|
+
loop do
|
|
53
|
+
skip_separators
|
|
54
|
+
break if eof?
|
|
55
|
+
|
|
56
|
+
begin
|
|
57
|
+
line = @line
|
|
58
|
+
value = parse_value
|
|
59
|
+
yielder << (locations ? ParsedValue.new(value: value, line: line) : value)
|
|
60
|
+
@input.discard_before(@index)
|
|
61
|
+
skip_whitespace
|
|
62
|
+
raise_error('expected record separator') if @seq && !(eof? || current == "\x1e")
|
|
63
|
+
rescue JSONParseError => e
|
|
64
|
+
raise unless @seq && @on_error
|
|
65
|
+
|
|
66
|
+
@on_error.call(e.message)
|
|
67
|
+
resync_to_record_separator
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def parse_value
|
|
76
|
+
skip_whitespace
|
|
77
|
+
raise_error('unexpected end of input') if eof?
|
|
78
|
+
|
|
79
|
+
case current
|
|
80
|
+
when '{'
|
|
81
|
+
parse_object
|
|
82
|
+
when '['
|
|
83
|
+
parse_array
|
|
84
|
+
when '"'
|
|
85
|
+
parse_string
|
|
86
|
+
when 't'
|
|
87
|
+
consume_literal('true', true)
|
|
88
|
+
when 'f'
|
|
89
|
+
consume_literal('false', false)
|
|
90
|
+
when 'n'
|
|
91
|
+
if @input[@index,
|
|
92
|
+
3].to_s.casecmp('nan').zero?
|
|
93
|
+
parse_special_number(positive: true)
|
|
94
|
+
else
|
|
95
|
+
consume_literal('null', nil)
|
|
96
|
+
end
|
|
97
|
+
when 'I', 'N'
|
|
98
|
+
parse_special_number(positive: true)
|
|
99
|
+
when '-'
|
|
100
|
+
if %w[I N].include?(@input[@index + 1, 1])
|
|
101
|
+
advance
|
|
102
|
+
parse_special_number(positive: false)
|
|
103
|
+
else
|
|
104
|
+
parse_number
|
|
105
|
+
end
|
|
106
|
+
else
|
|
107
|
+
parse_number
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def parse_object
|
|
112
|
+
with_container_depth { parse_object_body }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def parse_object_body
|
|
116
|
+
advance
|
|
117
|
+
object = {}
|
|
118
|
+
skip_whitespace
|
|
119
|
+
return object if consume?('}')
|
|
120
|
+
|
|
121
|
+
loop do
|
|
122
|
+
skip_whitespace
|
|
123
|
+
raise_error('expected object key string') unless current == '"'
|
|
124
|
+
|
|
125
|
+
key = parse_string
|
|
126
|
+
skip_whitespace
|
|
127
|
+
expect(':')
|
|
128
|
+
object[key] = parse_value
|
|
129
|
+
skip_whitespace
|
|
130
|
+
return object if consume?('}')
|
|
131
|
+
|
|
132
|
+
expect(',')
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def parse_array
|
|
137
|
+
with_container_depth { parse_array_body }
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def parse_array_body
|
|
141
|
+
advance
|
|
142
|
+
array = []
|
|
143
|
+
skip_whitespace
|
|
144
|
+
return array if consume?(']')
|
|
145
|
+
|
|
146
|
+
loop do
|
|
147
|
+
array << parse_value
|
|
148
|
+
skip_whitespace
|
|
149
|
+
return array if consume?(']')
|
|
150
|
+
|
|
151
|
+
expect(',')
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def parse_string
|
|
156
|
+
expect('"')
|
|
157
|
+
out = +''
|
|
158
|
+
bytes = 0
|
|
159
|
+
until eof?
|
|
160
|
+
line = @line
|
|
161
|
+
column = @column
|
|
162
|
+
char = advance
|
|
163
|
+
return out.force_encoding(Encoding::UTF_8) if char == '"'
|
|
164
|
+
|
|
165
|
+
piece = if char == '\\'
|
|
166
|
+
parse_escape
|
|
167
|
+
else
|
|
168
|
+
raise_error('unescaped control character in string') if char.ord < 0x20
|
|
169
|
+
|
|
170
|
+
char
|
|
171
|
+
end
|
|
172
|
+
bytes += piece.bytesize
|
|
173
|
+
raise_error_at("string exceeds #{@max_string_bytes} byte limit", line, column) if @max_string_bytes &&
|
|
174
|
+
bytes > @max_string_bytes
|
|
175
|
+
|
|
176
|
+
out << piece
|
|
177
|
+
end
|
|
178
|
+
raise_error('unterminated string')
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def parse_escape
|
|
182
|
+
raise_error('unterminated escape') if eof?
|
|
183
|
+
|
|
184
|
+
char = advance
|
|
185
|
+
case char
|
|
186
|
+
when '"', '\\', '/'
|
|
187
|
+
char
|
|
188
|
+
when 'b'
|
|
189
|
+
"\b"
|
|
190
|
+
when 'f'
|
|
191
|
+
"\f"
|
|
192
|
+
when 'n'
|
|
193
|
+
"\n"
|
|
194
|
+
when 'r'
|
|
195
|
+
"\r"
|
|
196
|
+
when 't'
|
|
197
|
+
"\t"
|
|
198
|
+
when 'u'
|
|
199
|
+
parse_unicode_escape
|
|
200
|
+
else
|
|
201
|
+
raise_error("invalid escape: \\#{char}")
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def parse_unicode_escape
|
|
206
|
+
codepoint = read_hex4
|
|
207
|
+
if high_surrogate?(codepoint)
|
|
208
|
+
raise_error('missing low surrogate') unless @input[@index, 2] == '\\u'
|
|
209
|
+
|
|
210
|
+
2.times { advance }
|
|
211
|
+
low = read_hex4
|
|
212
|
+
raise_error('invalid low surrogate') unless low_surrogate?(low)
|
|
213
|
+
|
|
214
|
+
codepoint = 0x10000 + ((codepoint - 0xD800) << 10) + (low - 0xDC00)
|
|
215
|
+
elsif low_surrogate?(codepoint)
|
|
216
|
+
raise_error('unexpected low surrogate')
|
|
217
|
+
end
|
|
218
|
+
[codepoint].pack('U')
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def parse_number
|
|
222
|
+
start = @index
|
|
223
|
+
digits = 0
|
|
224
|
+
consume?('-')
|
|
225
|
+
if current == '0'
|
|
226
|
+
digits = consume_number_digit(digits)
|
|
227
|
+
raise_error('leading zero in number') if digit?(current)
|
|
228
|
+
else
|
|
229
|
+
raise_error('expected number') unless digit_1_9?(current)
|
|
230
|
+
digits = consume_number_digit(digits) while digit?(current)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
if consume?('.')
|
|
234
|
+
raise_error('expected digit after decimal point') unless digit?(current)
|
|
235
|
+
digits = consume_number_digit(digits) while digit?(current)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
if %w[e E].include?(current)
|
|
239
|
+
advance
|
|
240
|
+
advance if ['+', '-'].include?(current)
|
|
241
|
+
raise_error('expected digit in exponent') unless digit?(current)
|
|
242
|
+
digits = consume_number_digit(digits) while digit?(current)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
literal = @input[start...@index]
|
|
246
|
+
raise_error('invalid number') unless number_delimiter?(current)
|
|
247
|
+
|
|
248
|
+
Number.parse(literal)
|
|
249
|
+
rescue ArgumentError
|
|
250
|
+
raise_error('invalid number')
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def number_delimiter?(char)
|
|
254
|
+
char.nil? || char.match?(/[\s,\]}\x1e]/)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def parse_special_number(positive:)
|
|
258
|
+
if @input[@index, 8] == 'Infinity'
|
|
259
|
+
8.times { advance }
|
|
260
|
+
raise_error('invalid number') if atom_char?(current)
|
|
261
|
+
|
|
262
|
+
return positive ? Float::INFINITY : -Float::INFINITY
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
raise_error('expected number') unless @input[@index, 3].to_s.casecmp('nan').zero?
|
|
266
|
+
|
|
267
|
+
3.times { advance }
|
|
268
|
+
digits = 0
|
|
269
|
+
digits = consume_number_digit(digits) while digit?(current)
|
|
270
|
+
raise_error('invalid number') if atom_char?(current)
|
|
271
|
+
|
|
272
|
+
Float::NAN
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def consume_literal(literal, value)
|
|
276
|
+
raise_error("expected #{literal}") unless @input[@index, literal.length] == literal
|
|
277
|
+
|
|
278
|
+
literal.length.times { advance }
|
|
279
|
+
raise_error("invalid literal #{literal}") if atom_char?(current)
|
|
280
|
+
|
|
281
|
+
value
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def atom_char?(char)
|
|
285
|
+
char&.match?(/[0-9A-Za-z_]/)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def read_hex4
|
|
289
|
+
chars = @input[@index, 4]
|
|
290
|
+
raise_error('invalid unicode escape') unless chars&.match?(/\A[0-9a-fA-F]{4}\z/)
|
|
291
|
+
|
|
292
|
+
4.times { advance }
|
|
293
|
+
chars.to_i(16)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def high_surrogate?(codepoint)
|
|
297
|
+
codepoint.between?(0xD800, 0xDBFF)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def low_surrogate?(codepoint)
|
|
301
|
+
codepoint.between?(0xDC00, 0xDFFF)
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def skip_separators
|
|
305
|
+
loop do
|
|
306
|
+
skip_whitespace
|
|
307
|
+
break unless @seq && current == "\x1e"
|
|
308
|
+
|
|
309
|
+
advance
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def resync_to_record_separator
|
|
314
|
+
advance until eof? || current == "\x1e"
|
|
315
|
+
@input.discard_before(@index)
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def with_container_depth
|
|
319
|
+
@depth += 1
|
|
320
|
+
raise_error('exceeds depth limit for parsing') if @depth > @max_depth
|
|
321
|
+
|
|
322
|
+
yield
|
|
323
|
+
ensure
|
|
324
|
+
@depth -= 1
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def consume_number_digit(count)
|
|
328
|
+
count += 1
|
|
329
|
+
if @max_number_digits && count > @max_number_digits
|
|
330
|
+
raise_error("number exceeds #{@max_number_digits} digit limit")
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
advance
|
|
334
|
+
count
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def validate_options!(chunk_size, max_depth, max_number_digits, max_string_bytes)
|
|
338
|
+
validate_nonnegative_integer!(:max_depth, max_depth)
|
|
339
|
+
validate_nonnegative_integer!(:max_number_digits, max_number_digits, optional: true)
|
|
340
|
+
validate_nonnegative_integer!(:max_string_bytes, max_string_bytes, optional: true)
|
|
341
|
+
return if chunk_size.is_a?(Integer) && chunk_size.positive?
|
|
342
|
+
|
|
343
|
+
raise ArgumentError, 'chunk_size must be a positive Integer'
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def validate_nonnegative_integer!(name, value, optional: false)
|
|
347
|
+
return if optional && value.nil?
|
|
348
|
+
return if value.is_a?(Integer) && value >= 0
|
|
349
|
+
|
|
350
|
+
raise ArgumentError, "#{name} must be a non-negative Integer#{' or nil' if optional}"
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def skip_whitespace
|
|
354
|
+
advance while current&.match?(/[ \t\r\n]/)
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def expect(char)
|
|
358
|
+
raise_error("expected #{char}") unless consume?(char)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def consume?(char)
|
|
362
|
+
return false unless current == char
|
|
363
|
+
|
|
364
|
+
advance
|
|
365
|
+
true
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def advance
|
|
369
|
+
char = current
|
|
370
|
+
@index += char.length
|
|
371
|
+
if char == "\n"
|
|
372
|
+
@line += 1
|
|
373
|
+
@column = 1
|
|
374
|
+
else
|
|
375
|
+
@column += 1
|
|
376
|
+
end
|
|
377
|
+
char
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def current
|
|
381
|
+
@input[@index]
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def eof?
|
|
385
|
+
current.nil?
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def digit?(char)
|
|
389
|
+
!char.nil? && char >= '0' && char <= '9'
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def digit_1_9?(char)
|
|
393
|
+
!char.nil? && char >= '1' && char <= '9'
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def raise_error(message)
|
|
397
|
+
raise JSONParseError, "#{message} at line #{@line}, column #{@column}"
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def raise_error_at(message, line, column)
|
|
401
|
+
raise JSONParseError, "#{message} at line #{line}, column #{column}"
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
end
|