kaitai-struct 0.9 → 0.10
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/LICENSE +21 -21
- data/README.md +44 -41
- data/lib/kaitai/struct/struct.rb +659 -621
- metadata +10 -9
- data/.gitignore +0 -1
- data/kaitai-struct.gemspec +0 -31
data/lib/kaitai/struct/struct.rb
CHANGED
|
@@ -1,621 +1,659 @@
|
|
|
1
|
-
require 'stringio'
|
|
2
|
-
|
|
3
|
-
module Kaitai
|
|
4
|
-
module Struct
|
|
5
|
-
|
|
6
|
-
VERSION = '0.
|
|
7
|
-
|
|
8
|
-
##
|
|
9
|
-
# Common base class for all structured generated by Kaitai Struct.
|
|
10
|
-
# Stores stream object that this object was parsed from in {#_io},
|
|
11
|
-
# stores reference to parent structure in {#_parent} and root
|
|
12
|
-
# structure in {#_root} and provides a few helper methods.
|
|
13
|
-
class Struct
|
|
14
|
-
|
|
15
|
-
def initialize(_io, _parent = nil, _root = self)
|
|
16
|
-
@_io = _io
|
|
17
|
-
@_parent = _parent
|
|
18
|
-
@_root = _root
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
##
|
|
22
|
-
# Factory method to instantiate a Kaitai Struct-powered structure,
|
|
23
|
-
# parsing it from a local file with a given filename.
|
|
24
|
-
# @param filename [String] local file to parse
|
|
25
|
-
def self.from_file(filename)
|
|
26
|
-
self.new(Stream.open(filename))
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
##
|
|
30
|
-
# Implementation of {Object#inspect} to aid debugging (at the very
|
|
31
|
-
# least, to aid exception raising) for KS-based classes. This one
|
|
32
|
-
# uses a bit terser syntax than Ruby's default one, purposely skips
|
|
33
|
-
# any internal fields (i.e. starting with `_`, such as `_io`,
|
|
34
|
-
# `_parent` and `_root`) to reduce confusion, and does no
|
|
35
|
-
# recursivity tracking (as proper general-purpose `inspect`
|
|
36
|
-
# implementation should do) because there are no endless recursion
|
|
37
|
-
# in KS-based classes by design (except for already mentioned
|
|
38
|
-
# internal navigation variables).
|
|
39
|
-
def inspect
|
|
40
|
-
vars = []
|
|
41
|
-
instance_variables.each { |nsym|
|
|
42
|
-
nstr = nsym.to_s
|
|
43
|
-
|
|
44
|
-
# skip all internal variables
|
|
45
|
-
next if nstr[0..1] == '@_'
|
|
46
|
-
|
|
47
|
-
# strip mandatory `@` at the beginning of the name for brevity
|
|
48
|
-
nstr = nstr[1..-1]
|
|
49
|
-
|
|
50
|
-
nvalue = instance_variable_get(nsym).inspect
|
|
51
|
-
|
|
52
|
-
vars << "#{nstr}=#{nvalue}"
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
"#{self.class}(#{vars.join(' ')})"
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
attr_reader :_io, :_parent, :_root
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
##
|
|
62
|
-
# Kaitai::Struct::Stream is an implementation of
|
|
63
|
-
# {https://
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
# would
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
@
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
#
|
|
111
|
-
# local file
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
#
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
#
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
#
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
#
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
#
|
|
213
|
-
#
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
a
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
#
|
|
236
|
-
#
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
#
|
|
263
|
-
#
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
#
|
|
275
|
-
#
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
@
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
mask = (1 << @bits_left) - 1
|
|
318
|
-
@bits &= mask
|
|
319
|
-
|
|
320
|
-
res
|
|
321
|
-
end
|
|
322
|
-
|
|
323
|
-
# Unused since Kaitai Struct Compiler v0.9+ - compatibility with
|
|
324
|
-
# older versions.
|
|
325
|
-
def read_bits_int(n)
|
|
326
|
-
read_bits_int_be(n)
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
def read_bits_int_le(n)
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
#
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
#
|
|
361
|
-
|
|
362
|
-
#
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
r
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
end
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
#
|
|
410
|
-
#
|
|
411
|
-
#
|
|
412
|
-
#
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
#
|
|
446
|
-
|
|
447
|
-
#
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
#
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
#
|
|
470
|
-
#
|
|
471
|
-
#
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
#
|
|
495
|
-
#
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
#
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
#
|
|
549
|
-
# a
|
|
550
|
-
#
|
|
551
|
-
|
|
552
|
-
def
|
|
553
|
-
|
|
554
|
-
end
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
#
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
end
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
#
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
end
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
#
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
end
|
|
587
|
-
end
|
|
588
|
-
|
|
589
|
-
##
|
|
590
|
-
#
|
|
591
|
-
#
|
|
592
|
-
class
|
|
593
|
-
def initialize(
|
|
594
|
-
super("
|
|
595
|
-
@
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
#
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
super("not
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
end
|
|
1
|
+
require 'stringio'
|
|
2
|
+
|
|
3
|
+
module Kaitai
|
|
4
|
+
module Struct
|
|
5
|
+
|
|
6
|
+
VERSION = '0.10'
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Common base class for all structured generated by Kaitai Struct.
|
|
10
|
+
# Stores stream object that this object was parsed from in {#_io},
|
|
11
|
+
# stores reference to parent structure in {#_parent} and root
|
|
12
|
+
# structure in {#_root} and provides a few helper methods.
|
|
13
|
+
class Struct
|
|
14
|
+
|
|
15
|
+
def initialize(_io, _parent = nil, _root = self)
|
|
16
|
+
@_io = _io
|
|
17
|
+
@_parent = _parent
|
|
18
|
+
@_root = _root
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Factory method to instantiate a Kaitai Struct-powered structure,
|
|
23
|
+
# parsing it from a local file with a given filename.
|
|
24
|
+
# @param filename [String] local file to parse
|
|
25
|
+
def self.from_file(filename)
|
|
26
|
+
self.new(Stream.open(filename))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Implementation of {Object#inspect} to aid debugging (at the very
|
|
31
|
+
# least, to aid exception raising) for KS-based classes. This one
|
|
32
|
+
# uses a bit terser syntax than Ruby's default one, purposely skips
|
|
33
|
+
# any internal fields (i.e. starting with `_`, such as `_io`,
|
|
34
|
+
# `_parent` and `_root`) to reduce confusion, and does no
|
|
35
|
+
# recursivity tracking (as proper general-purpose `inspect`
|
|
36
|
+
# implementation should do) because there are no endless recursion
|
|
37
|
+
# in KS-based classes by design (except for already mentioned
|
|
38
|
+
# internal navigation variables).
|
|
39
|
+
def inspect
|
|
40
|
+
vars = []
|
|
41
|
+
instance_variables.each { |nsym|
|
|
42
|
+
nstr = nsym.to_s
|
|
43
|
+
|
|
44
|
+
# skip all internal variables
|
|
45
|
+
next if nstr[0..1] == '@_'
|
|
46
|
+
|
|
47
|
+
# strip mandatory `@` at the beginning of the name for brevity
|
|
48
|
+
nstr = nstr[1..-1]
|
|
49
|
+
|
|
50
|
+
nvalue = instance_variable_get(nsym).inspect
|
|
51
|
+
|
|
52
|
+
vars << "#{nstr}=#{nvalue}"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
"#{self.class}(#{vars.join(' ')})"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
attr_reader :_io, :_parent, :_root
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# Kaitai::Struct::Stream is an implementation of
|
|
63
|
+
# {Kaitai Stream API}[https://doc.kaitai.io/stream_api.html] for Ruby.
|
|
64
|
+
# It's implemented as a wrapper for generic IO objects.
|
|
65
|
+
#
|
|
66
|
+
# It provides a wide variety of simple methods to read (parse) binary
|
|
67
|
+
# representations of primitive types, such as integer and floating
|
|
68
|
+
# point numbers, byte arrays and strings, and also provides stream
|
|
69
|
+
# positioning / navigation methods with unified cross-language and
|
|
70
|
+
# cross-toolkit semantics.
|
|
71
|
+
#
|
|
72
|
+
# Typically, end users won't access Kaitai Stream class manually, but
|
|
73
|
+
# would describe a binary structure format using .ksy language and
|
|
74
|
+
# then would use Kaitai Struct compiler to generate source code in
|
|
75
|
+
# desired target language. That code, in turn, would use this class
|
|
76
|
+
# and API to do the actual parsing job.
|
|
77
|
+
class Stream
|
|
78
|
+
##
|
|
79
|
+
# Unused since Kaitai Struct Compiler v0.9+ - compatibility with
|
|
80
|
+
# older versions.
|
|
81
|
+
#
|
|
82
|
+
# Exception class for an error that occurs when some fixed content
|
|
83
|
+
# was expected to appear, but actual data read was different.
|
|
84
|
+
class UnexpectedDataError < Exception
|
|
85
|
+
def initialize(actual, expected)
|
|
86
|
+
super("Unexpected fixed contents: got #{Stream.format_hex(actual)}, was waiting for #{Stream.format_hex(expected)}")
|
|
87
|
+
@actual = actual
|
|
88
|
+
@expected = expected
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
##
|
|
94
|
+
# Constructs new Kaitai Stream object.
|
|
95
|
+
# @param arg [String, IO] if String, it will be used as byte array to read data from;
|
|
96
|
+
# if IO, if will be used literally as source of data
|
|
97
|
+
def initialize(arg)
|
|
98
|
+
if arg.is_a?(String)
|
|
99
|
+
@_io = StringIO.new(arg)
|
|
100
|
+
elsif arg.is_a?(IO)
|
|
101
|
+
@_io = arg
|
|
102
|
+
else
|
|
103
|
+
raise TypeError.new('can be initialized with IO or String only')
|
|
104
|
+
end
|
|
105
|
+
align_to_byte
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
##
|
|
109
|
+
# Convenience method to create a Kaitai Stream object, opening a
|
|
110
|
+
# local file with a given filename.
|
|
111
|
+
# @param filename [String] local file to open
|
|
112
|
+
def self.open(filename)
|
|
113
|
+
self.new(File.open(filename, 'rb:ASCII-8BIT'))
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
##
|
|
117
|
+
# Closes underlying IO object.
|
|
118
|
+
def close
|
|
119
|
+
@_io.close
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Test endianness of the platform
|
|
123
|
+
@@big_endian = [0x0102].pack('s') == [0x0102].pack('n')
|
|
124
|
+
|
|
125
|
+
# @!group Stream positioning
|
|
126
|
+
|
|
127
|
+
##
|
|
128
|
+
# Check if stream pointer is at the end of stream.
|
|
129
|
+
# @return [true, false] true if we are located at the end of the stream
|
|
130
|
+
def eof?; @_io.eof? and @bits_left == 0; end
|
|
131
|
+
|
|
132
|
+
##
|
|
133
|
+
# Set stream pointer to designated position.
|
|
134
|
+
# @param x [Fixnum] new position (offset in bytes from the beginning of the stream)
|
|
135
|
+
def seek(x); @_io.seek(x); end
|
|
136
|
+
|
|
137
|
+
##
|
|
138
|
+
# Get current position of a stream pointer.
|
|
139
|
+
# @return [Fixnum] pointer position, number of bytes from the beginning of the stream
|
|
140
|
+
def pos; @_io.pos; end
|
|
141
|
+
|
|
142
|
+
##
|
|
143
|
+
# Get total size of the stream in bytes.
|
|
144
|
+
# @return [Fixnum] size of the stream in bytes
|
|
145
|
+
def size; @_io.size; end
|
|
146
|
+
|
|
147
|
+
# @!endgroup
|
|
148
|
+
|
|
149
|
+
# @!group Integer numbers
|
|
150
|
+
|
|
151
|
+
# ------------------------------------------------------------------------
|
|
152
|
+
# Signed
|
|
153
|
+
# ------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
def read_s1
|
|
156
|
+
read_bytes(1).unpack('c')[0]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# ........................................................................
|
|
160
|
+
# Big-endian
|
|
161
|
+
# ........................................................................
|
|
162
|
+
|
|
163
|
+
def read_s2be
|
|
164
|
+
to_signed(read_u2be, SIGN_MASK_16)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def read_s4be
|
|
168
|
+
to_signed(read_u4be, SIGN_MASK_32)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
if @@big_endian
|
|
172
|
+
def read_s8be
|
|
173
|
+
read_bytes(8).unpack('q')[0]
|
|
174
|
+
end
|
|
175
|
+
else
|
|
176
|
+
def read_s8be
|
|
177
|
+
to_signed(read_u8be, SIGN_MASK_64)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# ........................................................................
|
|
182
|
+
# Little-endian
|
|
183
|
+
# ........................................................................
|
|
184
|
+
|
|
185
|
+
def read_s2le
|
|
186
|
+
to_signed(read_u2le, SIGN_MASK_16)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def read_s4le
|
|
190
|
+
to_signed(read_u4le, SIGN_MASK_32)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
unless @@big_endian
|
|
194
|
+
def read_s8le
|
|
195
|
+
read_bytes(8).unpack('q')[0]
|
|
196
|
+
end
|
|
197
|
+
else
|
|
198
|
+
def read_s8le
|
|
199
|
+
to_signed(read_u8le, SIGN_MASK_64)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# ------------------------------------------------------------------------
|
|
204
|
+
# Unsigned
|
|
205
|
+
# ------------------------------------------------------------------------
|
|
206
|
+
|
|
207
|
+
def read_u1
|
|
208
|
+
read_bytes(1).unpack('C')[0]
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# ........................................................................
|
|
212
|
+
# Big-endian
|
|
213
|
+
# ........................................................................
|
|
214
|
+
|
|
215
|
+
def read_u2be
|
|
216
|
+
read_bytes(2).unpack('n')[0]
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def read_u4be
|
|
220
|
+
read_bytes(4).unpack('N')[0]
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
if @@big_endian
|
|
224
|
+
def read_u8be
|
|
225
|
+
read_bytes(8).unpack('Q')[0]
|
|
226
|
+
end
|
|
227
|
+
else
|
|
228
|
+
def read_u8be
|
|
229
|
+
a, b = read_bytes(8).unpack('NN')
|
|
230
|
+
(a << 32) + b
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# ........................................................................
|
|
235
|
+
# Little-endian
|
|
236
|
+
# ........................................................................
|
|
237
|
+
|
|
238
|
+
def read_u2le
|
|
239
|
+
read_bytes(2).unpack('v')[0]
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def read_u4le
|
|
243
|
+
read_bytes(4).unpack('V')[0]
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
unless @@big_endian
|
|
247
|
+
def read_u8le
|
|
248
|
+
read_bytes(8).unpack('Q')[0]
|
|
249
|
+
end
|
|
250
|
+
else
|
|
251
|
+
def read_u8le
|
|
252
|
+
a, b = read_bytes(8).unpack('VV')
|
|
253
|
+
(b << 32) + a
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# @!endgroup
|
|
258
|
+
|
|
259
|
+
# @!group Floating point numbers
|
|
260
|
+
|
|
261
|
+
# ------------------------------------------------------------------------
|
|
262
|
+
# Big-endian
|
|
263
|
+
# ------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
def read_f4be
|
|
266
|
+
read_bytes(4).unpack('g')[0]
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def read_f8be
|
|
270
|
+
read_bytes(8).unpack('G')[0]
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# ------------------------------------------------------------------------
|
|
274
|
+
# Little-endian
|
|
275
|
+
# ------------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
def read_f4le
|
|
278
|
+
read_bytes(4).unpack('e')[0]
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def read_f8le
|
|
282
|
+
read_bytes(8).unpack('E')[0]
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# @!endgroup
|
|
286
|
+
|
|
287
|
+
# @!group Unaligned bit values
|
|
288
|
+
|
|
289
|
+
def align_to_byte
|
|
290
|
+
@bits_left = 0
|
|
291
|
+
@bits = 0
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def read_bits_int_be(n)
|
|
295
|
+
res = 0
|
|
296
|
+
|
|
297
|
+
bits_needed = n - @bits_left
|
|
298
|
+
@bits_left = -bits_needed % 8
|
|
299
|
+
|
|
300
|
+
if bits_needed > 0
|
|
301
|
+
# 1 bit => 1 byte
|
|
302
|
+
# 8 bits => 1 byte
|
|
303
|
+
# 9 bits => 2 bytes
|
|
304
|
+
bytes_needed = ((bits_needed - 1) / 8) + 1 # `ceil(bits_needed / 8)`
|
|
305
|
+
buf = read_bytes(bytes_needed)
|
|
306
|
+
buf.each_byte { |byte|
|
|
307
|
+
res = res << 8 | byte
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
new_bits = res
|
|
311
|
+
res = res >> @bits_left | @bits << bits_needed
|
|
312
|
+
@bits = new_bits # will be masked at the end of the function
|
|
313
|
+
else
|
|
314
|
+
res = @bits >> -bits_needed # shift unneeded bits out
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
mask = (1 << @bits_left) - 1 # `@bits_left` is in range 0..7
|
|
318
|
+
@bits &= mask
|
|
319
|
+
|
|
320
|
+
res
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Unused since Kaitai Struct Compiler v0.9+ - compatibility with
|
|
324
|
+
# older versions.
|
|
325
|
+
def read_bits_int(n)
|
|
326
|
+
read_bits_int_be(n)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
def read_bits_int_le(n)
|
|
330
|
+
res = 0
|
|
331
|
+
bits_needed = n - @bits_left
|
|
332
|
+
|
|
333
|
+
if bits_needed > 0 then
|
|
334
|
+
# 1 bit => 1 byte
|
|
335
|
+
# 8 bits => 1 byte
|
|
336
|
+
# 9 bits => 2 bytes
|
|
337
|
+
bytes_needed = ((bits_needed - 1) / 8) + 1 # `ceil(bits_needed / 8)`
|
|
338
|
+
buf = read_bytes(bytes_needed)
|
|
339
|
+
i = 0
|
|
340
|
+
buf.each_byte { |byte|
|
|
341
|
+
res |= byte << (i * 8)
|
|
342
|
+
i += 1
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
new_bits = res >> bits_needed
|
|
346
|
+
res = res << @bits_left | @bits
|
|
347
|
+
@bits = new_bits
|
|
348
|
+
else
|
|
349
|
+
res = @bits
|
|
350
|
+
@bits >>= n
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
@bits_left = -bits_needed % 8
|
|
354
|
+
|
|
355
|
+
mask = (1 << n) - 1 # no problem with this in Ruby (arbitrary precision integers)
|
|
356
|
+
res &= mask
|
|
357
|
+
return res
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# @!endgroup
|
|
361
|
+
|
|
362
|
+
# @!group Byte arrays
|
|
363
|
+
|
|
364
|
+
##
|
|
365
|
+
# Reads designated number of bytes from the stream.
|
|
366
|
+
# @param n [Fixnum] number of bytes to read
|
|
367
|
+
# @return [String] read bytes as byte array
|
|
368
|
+
# @raise [EOFError] if there were less bytes than requested
|
|
369
|
+
# available in the stream
|
|
370
|
+
def read_bytes(n)
|
|
371
|
+
r = @_io.read(n)
|
|
372
|
+
if r
|
|
373
|
+
rl = r.bytesize
|
|
374
|
+
else
|
|
375
|
+
rl = 0
|
|
376
|
+
end
|
|
377
|
+
raise EOFError.new("attempted to read #{n} bytes, got only #{rl}") if rl < n
|
|
378
|
+
r
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
##
|
|
382
|
+
# Reads all the remaining bytes in a stream as byte array.
|
|
383
|
+
# @return [String] all remaining bytes in a stream as byte array
|
|
384
|
+
def read_bytes_full
|
|
385
|
+
@_io.read
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def read_bytes_term(term, include_term, consume_term, eos_error)
|
|
389
|
+
r = ''
|
|
390
|
+
loop {
|
|
391
|
+
if @_io.eof?
|
|
392
|
+
if eos_error
|
|
393
|
+
raise EOFError.new("end of stream reached, but no terminator #{term} found")
|
|
394
|
+
else
|
|
395
|
+
return r
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
c = @_io.getc
|
|
399
|
+
if c.ord == term
|
|
400
|
+
r << c if include_term
|
|
401
|
+
@_io.seek(@_io.pos - 1) unless consume_term
|
|
402
|
+
return r
|
|
403
|
+
end
|
|
404
|
+
r << c
|
|
405
|
+
}
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
##
|
|
409
|
+
# Unused since Kaitai Struct Compiler v0.9+ - compatibility with
|
|
410
|
+
# older versions.
|
|
411
|
+
#
|
|
412
|
+
# Reads next len bytes from the stream and ensures that they match
|
|
413
|
+
# expected fixed byte array. If they differ, throws a
|
|
414
|
+
# {UnexpectedDataError} runtime exception.
|
|
415
|
+
# @param expected [String] contents to be expected
|
|
416
|
+
# @return [String] read bytes as byte array, which are guaranteed to
|
|
417
|
+
# equal to expected
|
|
418
|
+
# @raise [UnexpectedDataError]
|
|
419
|
+
def ensure_fixed_contents(expected)
|
|
420
|
+
len = expected.bytesize
|
|
421
|
+
actual = @_io.read(len)
|
|
422
|
+
raise UnexpectedDataError.new(actual, expected) if actual != expected
|
|
423
|
+
actual
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def self.bytes_strip_right(bytes, pad_byte)
|
|
427
|
+
new_len = bytes.length
|
|
428
|
+
while new_len > 0 and bytes.getbyte(new_len - 1) == pad_byte
|
|
429
|
+
new_len -= 1
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
bytes[0, new_len]
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def self.bytes_terminate(bytes, term, include_term)
|
|
436
|
+
new_len = 0
|
|
437
|
+
max_len = bytes.length
|
|
438
|
+
while bytes.getbyte(new_len) != term and new_len < max_len
|
|
439
|
+
new_len += 1
|
|
440
|
+
end
|
|
441
|
+
new_len += 1 if include_term and new_len < max_len
|
|
442
|
+
bytes[0, new_len]
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# @!endgroup
|
|
446
|
+
|
|
447
|
+
# @!group Byte array processing
|
|
448
|
+
|
|
449
|
+
##
|
|
450
|
+
# Performs a XOR processing with given data, XORing every byte of
|
|
451
|
+
# input with a single given value. Uses pure Ruby implementation suggested
|
|
452
|
+
# by [Thomas Leitner](https://github.com/gettalong), borrowed from
|
|
453
|
+
# https://github.com/fny/xorcist/blob/master/bin/benchmark
|
|
454
|
+
# @param data [String] data to process
|
|
455
|
+
# @param key [Fixnum] value to XOR with
|
|
456
|
+
# @return [String] processed data
|
|
457
|
+
def self.process_xor_one(data, key)
|
|
458
|
+
out = data.dup
|
|
459
|
+
i = 0
|
|
460
|
+
max = data.length
|
|
461
|
+
while i < max
|
|
462
|
+
out.setbyte(i, data.getbyte(i) ^ key)
|
|
463
|
+
i += 1
|
|
464
|
+
end
|
|
465
|
+
out
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
##
|
|
469
|
+
# Performs a XOR processing with given data, XORing every byte of
|
|
470
|
+
# input with a key array, repeating key array many times, if
|
|
471
|
+
# necessary (i.e. if data array is longer than key array).
|
|
472
|
+
# Uses pure Ruby implementation suggested by
|
|
473
|
+
# [Thomas Leitner](https://github.com/gettalong), borrowed from
|
|
474
|
+
# https://github.com/fny/xorcist/blob/master/bin/benchmark
|
|
475
|
+
# @param data [String] data to process
|
|
476
|
+
# @param key [String] array of bytes to XOR with
|
|
477
|
+
# @return [String] processed data
|
|
478
|
+
def self.process_xor_many(data, key)
|
|
479
|
+
out = data.dup
|
|
480
|
+
kl = key.length
|
|
481
|
+
ki = 0
|
|
482
|
+
i = 0
|
|
483
|
+
max = data.length
|
|
484
|
+
while i < max
|
|
485
|
+
out.setbyte(i, data.getbyte(i) ^ key.getbyte(ki))
|
|
486
|
+
ki += 1
|
|
487
|
+
ki = 0 if ki >= kl
|
|
488
|
+
i += 1
|
|
489
|
+
end
|
|
490
|
+
out
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
##
|
|
494
|
+
# Performs a circular left rotation shift for a given buffer by a
|
|
495
|
+
# given amount of bits, using groups of groupSize bytes each
|
|
496
|
+
# time. Right circular rotation should be performed using this
|
|
497
|
+
# procedure with corrected amount.
|
|
498
|
+
# @param data [String] source data to process
|
|
499
|
+
# @param amount [Fixnum] number of bits to shift by
|
|
500
|
+
# @param group_size [Fixnum] number of bytes per group to shift
|
|
501
|
+
# @return [String] copy of source array with requested shift applied
|
|
502
|
+
def self.process_rotate_left(data, amount, group_size)
|
|
503
|
+
raise NotImplementedError.new("unable to rotate group #{group_size} bytes yet") unless group_size == 1
|
|
504
|
+
|
|
505
|
+
mask = group_size * 8 - 1
|
|
506
|
+
anti_amount = -amount & mask
|
|
507
|
+
|
|
508
|
+
# NB: actually, left bit shift (<<) in Ruby would have required
|
|
509
|
+
# truncation to type_bits size (i.e. something like "& 0xff" for
|
|
510
|
+
# group_size == 8), but we can skip this one, because later these
|
|
511
|
+
# number would be packed with Array#pack, which will do truncation
|
|
512
|
+
# anyway
|
|
513
|
+
|
|
514
|
+
data.bytes.map { |x| (x << amount) | (x >> anti_amount) }.pack('C*')
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# @!endgroup
|
|
518
|
+
|
|
519
|
+
##
|
|
520
|
+
# Resolves value using enum: if the value is not found in the map,
|
|
521
|
+
# we'll just use literal value per se.
|
|
522
|
+
def self.resolve_enum(enum_map, value)
|
|
523
|
+
enum_map[value] || value
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
# ========================================================================
|
|
527
|
+
|
|
528
|
+
private
|
|
529
|
+
SIGN_MASK_16 = (1 << (16 - 1))
|
|
530
|
+
SIGN_MASK_32 = (1 << (32 - 1))
|
|
531
|
+
SIGN_MASK_64 = (1 << (64 - 1))
|
|
532
|
+
|
|
533
|
+
def to_signed(x, mask)
|
|
534
|
+
(x & ~mask) - (x & mask)
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def self.format_hex(bytes)
|
|
538
|
+
bytes.unpack('H*')[0].gsub(/(..)/, '\1 ').chop
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
###
|
|
542
|
+
# Guess if the given args are most likely byte arrays.
|
|
543
|
+
# <p>
|
|
544
|
+
# There's no way to know for sure, but {@code Encoding::ASCII_8BIT} is a special encoding that is
|
|
545
|
+
# usually used for a byte array(/string), not a character string. For those reasons, that encoding
|
|
546
|
+
# is NOT planned to be allowed for human readable texts by KS in general as well.
|
|
547
|
+
# </p>
|
|
548
|
+
# @param args [...] Something to check.
|
|
549
|
+
# @see <a href="https://ruby-doc.org/core-3.0.0/Encoding.html">Encoding</a>
|
|
550
|
+
# @see <a href="https://github.com/kaitai-io/kaitai_struct/issues/116">List of supported encodings</a>
|
|
551
|
+
#
|
|
552
|
+
def self.is_byte_array?(*args)
|
|
553
|
+
args.all? { |arg| arg.is_a?(String) and (arg.encoding == Encoding::ASCII_8BIT) }
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def self.inspect_values(*args)
|
|
557
|
+
reprs = args.map { |arg|
|
|
558
|
+
if Stream.is_byte_array?(arg)
|
|
559
|
+
"[#{Stream.format_hex(arg)}]"
|
|
560
|
+
else
|
|
561
|
+
arg.inspect
|
|
562
|
+
end
|
|
563
|
+
}
|
|
564
|
+
reprs.length == 1 ? reprs[0] : reprs
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
##
|
|
569
|
+
# Common ancestor for all error originating from Kaitai Struct usage.
|
|
570
|
+
# Stores KSY source path, pointing to an element supposedly guilty of
|
|
571
|
+
# an error.
|
|
572
|
+
class KaitaiStructError < Exception
|
|
573
|
+
def initialize(msg, src_path)
|
|
574
|
+
super("#{src_path}: #{msg}")
|
|
575
|
+
@src_path = src_path
|
|
576
|
+
end
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
##
|
|
580
|
+
# Error that occurs when default endianness should be decided with
|
|
581
|
+
# a switch, but nothing matches (although using endianness expression
|
|
582
|
+
# implies that there should be some positive result).
|
|
583
|
+
class UndecidedEndiannessError < KaitaiStructError
|
|
584
|
+
def initialize(src_path)
|
|
585
|
+
super("unable to decide on endianness for a type", src_path)
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
##
|
|
590
|
+
# Common ancestor for all validation failures. Stores pointer to
|
|
591
|
+
# KaitaiStream IO object which was involved in an error.
|
|
592
|
+
class ValidationFailedError < KaitaiStructError
|
|
593
|
+
def initialize(msg, io, src_path)
|
|
594
|
+
super("at pos #{io.pos}: validation failed: #{msg}", src_path)
|
|
595
|
+
@io = io
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
##
|
|
600
|
+
# Signals validation failure: we required "actual" value to be equal to
|
|
601
|
+
# "expected", but it turned out that it's not.
|
|
602
|
+
class ValidationNotEqualError < ValidationFailedError
|
|
603
|
+
def initialize(expected, actual, io, src_path)
|
|
604
|
+
expected_repr, actual_repr = Stream.inspect_values(expected, actual)
|
|
605
|
+
super("not equal, expected #{expected_repr}, but got #{actual_repr}", io, src_path)
|
|
606
|
+
|
|
607
|
+
@expected = expected
|
|
608
|
+
@actual = actual
|
|
609
|
+
end
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
##
|
|
613
|
+
# Signals validation failure: we required "actual" value to be greater
|
|
614
|
+
# than or equal to "min", but it turned out that it's not.
|
|
615
|
+
class ValidationLessThanError < ValidationFailedError
|
|
616
|
+
def initialize(min, actual, io, src_path)
|
|
617
|
+
min_repr, actual_repr = Stream.inspect_values(min, actual)
|
|
618
|
+
super("not in range, min #{min_repr}, but got #{actual_repr}", io, src_path)
|
|
619
|
+
@min = min
|
|
620
|
+
@actual = actual
|
|
621
|
+
end
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
##
|
|
625
|
+
# Signals validation failure: we required "actual" value to be less
|
|
626
|
+
# than or equal to "max", but it turned out that it's not.
|
|
627
|
+
class ValidationGreaterThanError < ValidationFailedError
|
|
628
|
+
def initialize(max, actual, io, src_path)
|
|
629
|
+
max_repr, actual_repr = Stream.inspect_values(max, actual)
|
|
630
|
+
super("not in range, max #{max_repr}, but got #{actual_repr}", io, src_path)
|
|
631
|
+
@max = max
|
|
632
|
+
@actual = actual
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
##
|
|
637
|
+
# Signals validation failure: we required "actual" value to be any of
|
|
638
|
+
# the given list, but it turned out that it's not.
|
|
639
|
+
class ValidationNotAnyOfError < ValidationFailedError
|
|
640
|
+
def initialize(actual, io, src_path)
|
|
641
|
+
actual_repr = Stream.inspect_values(actual)
|
|
642
|
+
super("not any of the list, got #{actual_repr}", io, src_path)
|
|
643
|
+
@actual = actual
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
##
|
|
648
|
+
# Signals validation failure: we required "actual" value to match
|
|
649
|
+
# the expression, but it turned out that it doesn't.
|
|
650
|
+
class ValidationExprError < ValidationFailedError
|
|
651
|
+
def initialize(actual, io, src_path)
|
|
652
|
+
actual_repr = Stream.inspect_values(actual)
|
|
653
|
+
super("not matching the expression, got #{actual_repr}", io, src_path)
|
|
654
|
+
@actual = actual
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
end
|
|
659
|
+
end
|