marshalsea 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/LICENSE +661 -0
- data/README.md +220 -0
- data/lib/marshalsea/chains/base.rb +102 -0
- data/lib/marshalsea/chains/erb_def_method.rb +72 -0
- data/lib/marshalsea/chains/erb_def_module.rb +129 -0
- data/lib/marshalsea/chains/psych_init_with.rb +57 -0
- data/lib/marshalsea/chains.rb +38 -0
- data/lib/marshalsea/marshal/boundary_detector.rb +186 -0
- data/lib/marshalsea/marshal/constants.rb +91 -0
- data/lib/marshalsea/marshal/errors.rb +29 -0
- data/lib/marshalsea/marshal/float_body.rb +40 -0
- data/lib/marshalsea/marshal/limits.rb +172 -0
- data/lib/marshalsea/marshal/load_guard.rb +120 -0
- data/lib/marshalsea/marshal/node.rb +174 -0
- data/lib/marshalsea/marshal/parser.rb +309 -0
- data/lib/marshalsea/psych/inspector.rb +277 -0
- data/lib/marshalsea/scanner.rb +397 -0
- data/lib/marshalsea/version.rb +7 -0
- data/lib/marshalsea.rb +19 -0
- metadata +66 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# ©AngelaMos | 2026
|
|
2
|
+
# node.rb
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
module Marshalsea
|
|
6
|
+
module Marshal
|
|
7
|
+
class Node
|
|
8
|
+
STRING_BACKED_TYPES = %i[string regexp].freeze
|
|
9
|
+
WRAPPER_TYPES = %i[user_class extended].freeze
|
|
10
|
+
|
|
11
|
+
attr_reader :type, :tag, :children, :instance_variables_map, :instance_variable_pairs,
|
|
12
|
+
:auxiliary, :undecoded_tail
|
|
13
|
+
attr_accessor :value, :class_name, :link_target, :regexp_options
|
|
14
|
+
|
|
15
|
+
def initialize(type:, tag: nil, value: nil, class_name: nil, undecoded_tail: nil)
|
|
16
|
+
@type = type
|
|
17
|
+
@tag = tag
|
|
18
|
+
@value = value
|
|
19
|
+
@class_name = class_name
|
|
20
|
+
@undecoded_tail = undecoded_tail
|
|
21
|
+
@children = []
|
|
22
|
+
@instance_variables_map = {}
|
|
23
|
+
@instance_variable_pairs = []
|
|
24
|
+
@auxiliary = []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def fully_decoded?
|
|
28
|
+
undecoded_tail.nil?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def sink?
|
|
32
|
+
Constants::SINK_TAGS.include?(tag)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def sink_method
|
|
36
|
+
Constants::SINK_METHODS[tag]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def gated?
|
|
40
|
+
Constants::GATED_SINK_TAGS.include?(tag)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def dispatches_key_methods?
|
|
44
|
+
!hash_dispatcher.nil? || !eql_dispatcher.nil?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def hash_dispatcher(seen = {}.compare_by_identity)
|
|
48
|
+
return nil if seen.key?(self)
|
|
49
|
+
|
|
50
|
+
seen[self] = true
|
|
51
|
+
return link_target&.hash_dispatcher(seen) if type == :object_link
|
|
52
|
+
return member_dispatcher(:hash_dispatcher, seen) unless class_name
|
|
53
|
+
return nil if WRAPPER_TYPES.include?(type) && string_backed?
|
|
54
|
+
|
|
55
|
+
self
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def eql_dispatcher(seen = {}.compare_by_identity)
|
|
59
|
+
return nil if seen.key?(self)
|
|
60
|
+
|
|
61
|
+
seen[self] = true
|
|
62
|
+
return link_target&.eql_dispatcher(seen) if type == :object_link
|
|
63
|
+
return member_dispatcher(:eql_dispatcher, seen) unless class_name
|
|
64
|
+
|
|
65
|
+
self
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def member_dispatcher(probe, seen)
|
|
69
|
+
children.each do |child|
|
|
70
|
+
found = child.public_send(probe, seen)
|
|
71
|
+
return found if found
|
|
72
|
+
end
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def range_endpoints
|
|
77
|
+
instance_variable_pairs.filter_map do |name, value|
|
|
78
|
+
next unless Constants::RANGE_ENDPOINT_IVARS.include?(name.value)
|
|
79
|
+
|
|
80
|
+
value if value.effective_class_name
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def effective_class_name
|
|
85
|
+
link_target ? link_target.class_name : class_name
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def string_backed?
|
|
89
|
+
wrapped = children.first
|
|
90
|
+
return false unless wrapped
|
|
91
|
+
|
|
92
|
+
STRING_BACKED_TYPES.include?(wrapped.type)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def each(&block)
|
|
96
|
+
return enum_for(:each) unless block
|
|
97
|
+
|
|
98
|
+
yield self
|
|
99
|
+
children.each { |child| child.each(&block) }
|
|
100
|
+
auxiliary.each { |child| child.each(&block) }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def seal
|
|
104
|
+
value.freeze
|
|
105
|
+
class_name.freeze
|
|
106
|
+
undecoded_tail.freeze
|
|
107
|
+
children.freeze
|
|
108
|
+
auxiliary.freeze
|
|
109
|
+
instance_variables_map.freeze
|
|
110
|
+
instance_variable_pairs.freeze
|
|
111
|
+
freeze
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class Result
|
|
116
|
+
attr_reader :root, :major, :minor, :role_anomalies
|
|
117
|
+
|
|
118
|
+
def initialize(root, major:, minor:, role_anomalies: [])
|
|
119
|
+
@root = root
|
|
120
|
+
@major = major
|
|
121
|
+
@minor = minor
|
|
122
|
+
@role_anomalies = role_anomalies
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def canonical_version?
|
|
126
|
+
major == Constants::MAJOR_VERSION && minor == Constants::MINOR_VERSION
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def canonical_roles?
|
|
130
|
+
role_anomalies.empty?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def nodes
|
|
134
|
+
root.each
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def class_names
|
|
138
|
+
nodes.filter_map(&:class_name).uniq
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def sinks
|
|
142
|
+
nodes.select(&:sink?)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def gated_sinks
|
|
146
|
+
sinks.select(&:gated?)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def hash_keys
|
|
150
|
+
nodes.select { |node| node.type == :hash }
|
|
151
|
+
.flat_map(&:children)
|
|
152
|
+
.select { |child| child.type == :pair }
|
|
153
|
+
.filter_map { |pair| pair.children.first }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def dispatching_hash_keys
|
|
157
|
+
hash_keys.select(&:dispatches_key_methods?)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def hash_dispatching_keys
|
|
161
|
+
hash_keys.filter_map(&:hash_dispatcher)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def eql_dispatching_keys
|
|
165
|
+
hash_keys.filter_map(&:eql_dispatcher)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def range_endpoint_dispatchers
|
|
169
|
+
nodes.select { |node| node.effective_class_name == Constants::RANGE_CLASS_NAME }
|
|
170
|
+
.flat_map(&:range_endpoints)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# ©AngelaMos | 2026
|
|
2
|
+
# parser.rb
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
module Marshalsea
|
|
6
|
+
module Marshal
|
|
7
|
+
class Parser
|
|
8
|
+
include Constants
|
|
9
|
+
|
|
10
|
+
def initialize(source, max_depth: nil, limits: Limits.new)
|
|
11
|
+
raise InputTypeError, "expected String, got #{source.class}" unless source.is_a?(String)
|
|
12
|
+
|
|
13
|
+
@limits = limits
|
|
14
|
+
@max_depth = max_depth || limits.max_depth
|
|
15
|
+
enforce_size(source)
|
|
16
|
+
@source = source.dup.force_encoding(Encoding::BINARY)
|
|
17
|
+
@budget = Budget.new(limits)
|
|
18
|
+
@position = 0
|
|
19
|
+
@symbols = []
|
|
20
|
+
@objects = []
|
|
21
|
+
@role_anomalies = []
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def parse
|
|
25
|
+
read_header
|
|
26
|
+
root = read_value(1)
|
|
27
|
+
raise TrailingBytesError, "#{remaining} unread bytes" unless remaining.zero?
|
|
28
|
+
|
|
29
|
+
root.each(&:seal)
|
|
30
|
+
Result.new(root, major: @major, minor: @minor,
|
|
31
|
+
role_anomalies: @role_anomalies.freeze).freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
attr_reader :source, :max_depth, :symbols, :objects, :budget, :limits
|
|
37
|
+
|
|
38
|
+
def enforce_size(candidate)
|
|
39
|
+
return if candidate.bytesize <= limits.max_bytes
|
|
40
|
+
|
|
41
|
+
raise LimitExceededError, "#{Limits::ROLE_BYTES} #{candidate.bytesize} exceeds #{limits.max_bytes}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def remaining
|
|
45
|
+
source.bytesize - @position
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def take(count)
|
|
49
|
+
raise MalformedCountError, "negative byte count #{count}" if count.negative?
|
|
50
|
+
raise TruncatedStreamError, "wanted #{count} bytes, had #{remaining}" if count > remaining
|
|
51
|
+
|
|
52
|
+
slice = source.byteslice(@position, count)
|
|
53
|
+
@position += count
|
|
54
|
+
slice
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def read_count(role)
|
|
58
|
+
value = read_fixnum
|
|
59
|
+
raise MalformedCountError, "negative #{role} count #{value}" if value.negative?
|
|
60
|
+
|
|
61
|
+
value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def take_byte
|
|
65
|
+
take(1).unpack1("C")
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def take_signed_byte
|
|
69
|
+
byte = take_byte
|
|
70
|
+
byte >= BYTE_SIGN_THRESHOLD ? byte - BYTE_MODULUS : byte
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def read_header
|
|
74
|
+
@major, @minor = take(HEADER_LENGTH).unpack("CC")
|
|
75
|
+
return if @major == MAJOR_VERSION && @minor <= MINOR_VERSION
|
|
76
|
+
|
|
77
|
+
raise UnsupportedVersionError, "stream declares #{@major}.#{@minor}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def read_fixnum
|
|
81
|
+
marker = take_signed_byte
|
|
82
|
+
return 0 if marker.zero?
|
|
83
|
+
return marker - FIXNUM_INLINE_OFFSET if marker > FIXNUM_MAX_INLINE
|
|
84
|
+
return marker + FIXNUM_INLINE_OFFSET if marker < FIXNUM_MIN_INLINE
|
|
85
|
+
|
|
86
|
+
width = marker.abs
|
|
87
|
+
value = little_endian(take(width))
|
|
88
|
+
marker.negative? ? value - (1 << (BITS_PER_BYTE * width)) : value
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def little_endian(bytes)
|
|
92
|
+
bytes.each_byte.with_index.sum { |byte, index| byte << (BITS_PER_BYTE * index) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def read_counted_bytes
|
|
96
|
+
size = read_count(ROLE_LENGTH)
|
|
97
|
+
budget.scalar!(size)
|
|
98
|
+
take(size)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def read_entry_count(role)
|
|
102
|
+
count = read_count(role)
|
|
103
|
+
budget.entries!(count)
|
|
104
|
+
count
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def register(node)
|
|
108
|
+
budget.registered!
|
|
109
|
+
objects << node
|
|
110
|
+
node
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def read_value(depth)
|
|
114
|
+
raise DepthLimitError, "exceeded depth #{max_depth}" if depth > max_depth
|
|
115
|
+
|
|
116
|
+
budget.node!
|
|
117
|
+
tag = take(1)
|
|
118
|
+
|
|
119
|
+
case tag
|
|
120
|
+
when TAG_NIL then Node.new(type: :nil, tag: tag)
|
|
121
|
+
when TAG_TRUE then Node.new(type: :boolean, tag: tag, value: true)
|
|
122
|
+
when TAG_FALSE then Node.new(type: :boolean, tag: tag, value: false)
|
|
123
|
+
when TAG_FIXNUM then Node.new(type: :fixnum, tag: tag, value: read_fixnum)
|
|
124
|
+
when TAG_SYMBOL then read_symbol(tag)
|
|
125
|
+
when TAG_SYMLINK then read_symlink(tag)
|
|
126
|
+
when TAG_OBJECT_LINK then read_object_link(tag)
|
|
127
|
+
when TAG_BIGNUM then register(read_bignum(tag))
|
|
128
|
+
when TAG_FLOAT then register(read_float(tag))
|
|
129
|
+
when TAG_STRING then register(read_string(tag))
|
|
130
|
+
when TAG_REGEXP then register(read_regexp(tag))
|
|
131
|
+
when TAG_ARRAY then read_array(tag, depth)
|
|
132
|
+
when TAG_HASH, TAG_HASH_DEFAULT then read_hash(tag, depth)
|
|
133
|
+
when TAG_IVAR then read_ivar(tag, depth)
|
|
134
|
+
when TAG_OBJECT then read_object(tag, depth)
|
|
135
|
+
when TAG_STRUCT then read_struct(tag, depth)
|
|
136
|
+
when TAG_USERDEF then register(read_userdef(tag, depth))
|
|
137
|
+
when TAG_USERMARSHAL then read_usermarshal(tag, depth)
|
|
138
|
+
when TAG_DATA then read_wrapped(tag, :data, depth)
|
|
139
|
+
when TAG_USERCLASS then read_wrapped(tag, :user_class, depth)
|
|
140
|
+
when TAG_EXTENDED then read_wrapped(tag, :extended, depth)
|
|
141
|
+
when TAG_CLASS then register(read_named(tag, :class))
|
|
142
|
+
when TAG_MODULE, TAG_MODULE_OLD then register(read_named(tag, :module))
|
|
143
|
+
else raise UnknownTagError, "byte #{tag.unpack1('C')} at offset #{@position - 1}"
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def read_symbol(tag)
|
|
148
|
+
budget.symbol!
|
|
149
|
+
size = read_count(ROLE_LENGTH)
|
|
150
|
+
budget.symbol_name!(size)
|
|
151
|
+
budget.scalar!(size)
|
|
152
|
+
node = Node.new(type: :symbol, tag: tag, value: take(size).to_sym)
|
|
153
|
+
symbols << node.value
|
|
154
|
+
node
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def read_symlink(tag)
|
|
158
|
+
budget.symbol_reference!
|
|
159
|
+
index = read_fixnum
|
|
160
|
+
raise InvalidLinkError, "symlink #{index} of #{symbols.length}" if
|
|
161
|
+
index.negative? || index >= symbols.length
|
|
162
|
+
|
|
163
|
+
Node.new(type: :symlink, tag: tag, value: symbols[index])
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def read_object_link(tag)
|
|
167
|
+
budget.link!
|
|
168
|
+
index = read_fixnum
|
|
169
|
+
raise InvalidLinkError, "object link #{index} of #{objects.length}" if
|
|
170
|
+
index.negative? || index >= objects.length
|
|
171
|
+
|
|
172
|
+
node = Node.new(type: :object_link, tag: tag, value: index)
|
|
173
|
+
node.link_target = objects[index]
|
|
174
|
+
node
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def read_bignum(tag)
|
|
178
|
+
sign = take(1)
|
|
179
|
+
raise MalformedValueError, "bignum sign #{sign.inspect}" unless BIGNUM_SIGNS.include?(sign)
|
|
180
|
+
|
|
181
|
+
size = read_count(ROLE_BIGNUM) * BIGNUM_WORD_BYTES
|
|
182
|
+
budget.scalar!(size)
|
|
183
|
+
magnitude = little_endian(take(size))
|
|
184
|
+
Node.new(type: :bignum, tag: tag,
|
|
185
|
+
value: sign == BIGNUM_SIGN_NEGATIVE ? -magnitude : magnitude)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def read_float(tag)
|
|
189
|
+
value, tail = FloatBody.decode(read_counted_bytes)
|
|
190
|
+
Node.new(type: :float, tag: tag, value: value, undecoded_tail: tail)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def read_string(tag)
|
|
194
|
+
Node.new(type: :string, tag: tag, value: read_counted_bytes)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def read_regexp(tag)
|
|
198
|
+
node = Node.new(type: :regexp, tag: tag, value: read_counted_bytes)
|
|
199
|
+
node.regexp_options = take_byte
|
|
200
|
+
node
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def read_array(tag, depth)
|
|
204
|
+
node = register(Node.new(type: :array, tag: tag))
|
|
205
|
+
read_entry_count(ROLE_ARRAY).times { node.children << read_value(depth + 1) }
|
|
206
|
+
node
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def read_hash(tag, depth)
|
|
210
|
+
node = register(Node.new(type: :hash, tag: tag))
|
|
211
|
+
read_entry_count(ROLE_HASH).times { node.children << read_pair(depth) }
|
|
212
|
+
node.children << read_value(depth + 1) if tag == TAG_HASH_DEFAULT
|
|
213
|
+
node
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def read_pair(depth)
|
|
217
|
+
pair = Node.new(type: :pair)
|
|
218
|
+
pair.children << read_value(depth + 1)
|
|
219
|
+
pair.children << read_value(depth + 1)
|
|
220
|
+
pair
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def note_role_anomaly(role, node)
|
|
224
|
+
return if CLASS_NAME_TYPES.include?(node.type)
|
|
225
|
+
|
|
226
|
+
@role_anomalies << format(ROLE_ANOMALY, role, node.type).freeze
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def read_class_name(node, depth)
|
|
230
|
+
class_node = read_value(depth)
|
|
231
|
+
unless CLASS_NAME_TYPES.include?(class_node.type)
|
|
232
|
+
raise MalformedValueError,
|
|
233
|
+
"class name slot holds #{class_node.type}, not a symbol"
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
node.class_name = class_node.value.to_s
|
|
237
|
+
node.auxiliary << class_node
|
|
238
|
+
node
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def read_instance_variables(node, depth)
|
|
242
|
+
count = read_entry_count(ROLE_IVAR)
|
|
243
|
+
budget.instance_variables!(count)
|
|
244
|
+
count.times do
|
|
245
|
+
name = read_value(depth + 1)
|
|
246
|
+
value = read_value(depth + 1)
|
|
247
|
+
note_role_anomaly(ROLE_IVAR, name)
|
|
248
|
+
node.auxiliary << name
|
|
249
|
+
node.auxiliary << value
|
|
250
|
+
node.instance_variables_map[name.value] = value
|
|
251
|
+
node.instance_variable_pairs << [name, value].freeze
|
|
252
|
+
end
|
|
253
|
+
node
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def read_ivar(_tag, depth)
|
|
257
|
+
read_instance_variables(read_value(depth + 1), depth)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def read_object(tag, depth)
|
|
261
|
+
node = register(Node.new(type: :object, tag: tag))
|
|
262
|
+
read_class_name(node, depth + 1)
|
|
263
|
+
read_instance_variables(node, depth)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def read_struct(tag, depth)
|
|
267
|
+
node = register(Node.new(type: :struct, tag: tag))
|
|
268
|
+
read_class_name(node, depth + 1)
|
|
269
|
+
count = read_entry_count(ROLE_STRUCT)
|
|
270
|
+
budget.struct_members!(count)
|
|
271
|
+
count.times do
|
|
272
|
+
pair = read_pair(depth)
|
|
273
|
+
note_role_anomaly(ROLE_STRUCT, pair.children.first)
|
|
274
|
+
node.children << pair
|
|
275
|
+
end
|
|
276
|
+
node
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def read_userdef(tag, depth)
|
|
280
|
+
node = Node.new(type: :userdef, tag: tag)
|
|
281
|
+
read_class_name(node, depth + 1)
|
|
282
|
+
node.value = read_counted_bytes
|
|
283
|
+
node
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def read_usermarshal(tag, depth)
|
|
287
|
+
node = register(Node.new(type: :usermarshal, tag: tag))
|
|
288
|
+
read_class_name(node, depth + 1)
|
|
289
|
+
node.children << read_value(depth + 1)
|
|
290
|
+
node
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def read_wrapped(tag, type, depth)
|
|
294
|
+
node = Node.new(type: type, tag: tag)
|
|
295
|
+
register(node) if REGISTERED_WRAPPER_TYPES.include?(type)
|
|
296
|
+
read_class_name(node, depth + 1)
|
|
297
|
+
node.children << read_value(depth + 1)
|
|
298
|
+
node
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def read_named(tag, type)
|
|
302
|
+
size = read_count(ROLE_LENGTH)
|
|
303
|
+
budget.class_name!(size)
|
|
304
|
+
budget.scalar!(size)
|
|
305
|
+
Node.new(type: type, tag: tag, class_name: take(size))
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|