unibuf 0.1.0 → 0.1.2
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/.rubocop_todo.yml +178 -330
- data/CODE_OF_CONDUCT.md +132 -0
- data/README.adoc +443 -254
- data/docs/CAPNPROTO.adoc +436 -0
- data/docs/FLATBUFFERS.adoc +430 -0
- data/docs/PROTOBUF.adoc +515 -0
- data/docs/TXTPROTO.adoc +369 -0
- data/lib/unibuf/commands/convert.rb +60 -2
- data/lib/unibuf/commands/schema.rb +68 -11
- data/lib/unibuf/errors.rb +23 -26
- data/lib/unibuf/models/capnproto/enum_definition.rb +72 -0
- data/lib/unibuf/models/capnproto/field_definition.rb +81 -0
- data/lib/unibuf/models/capnproto/interface_definition.rb +70 -0
- data/lib/unibuf/models/capnproto/method_definition.rb +81 -0
- data/lib/unibuf/models/capnproto/schema.rb +84 -0
- data/lib/unibuf/models/capnproto/struct_definition.rb +96 -0
- data/lib/unibuf/models/capnproto/union_definition.rb +62 -0
- data/lib/unibuf/models/flatbuffers/enum_definition.rb +69 -0
- data/lib/unibuf/models/flatbuffers/field_definition.rb +88 -0
- data/lib/unibuf/models/flatbuffers/schema.rb +102 -0
- data/lib/unibuf/models/flatbuffers/struct_definition.rb +70 -0
- data/lib/unibuf/models/flatbuffers/table_definition.rb +73 -0
- data/lib/unibuf/models/flatbuffers/union_definition.rb +60 -0
- data/lib/unibuf/models/message.rb +10 -0
- data/lib/unibuf/models/values/scalar_value.rb +2 -2
- data/lib/unibuf/parsers/binary/wire_format_parser.rb +199 -19
- data/lib/unibuf/parsers/capnproto/binary_parser.rb +267 -0
- data/lib/unibuf/parsers/capnproto/grammar.rb +272 -0
- data/lib/unibuf/parsers/capnproto/list_reader.rb +208 -0
- data/lib/unibuf/parsers/capnproto/pointer_decoder.rb +163 -0
- data/lib/unibuf/parsers/capnproto/processor.rb +348 -0
- data/lib/unibuf/parsers/capnproto/segment_reader.rb +131 -0
- data/lib/unibuf/parsers/capnproto/struct_reader.rb +199 -0
- data/lib/unibuf/parsers/flatbuffers/binary_parser.rb +325 -0
- data/lib/unibuf/parsers/flatbuffers/grammar.rb +235 -0
- data/lib/unibuf/parsers/flatbuffers/processor.rb +299 -0
- data/lib/unibuf/parsers/textproto/grammar.rb +1 -1
- data/lib/unibuf/parsers/textproto/processor.rb +10 -0
- data/lib/unibuf/serializers/binary_serializer.rb +218 -0
- data/lib/unibuf/serializers/capnproto/binary_serializer.rb +402 -0
- data/lib/unibuf/serializers/capnproto/list_writer.rb +199 -0
- data/lib/unibuf/serializers/capnproto/pointer_encoder.rb +118 -0
- data/lib/unibuf/serializers/capnproto/segment_builder.rb +124 -0
- data/lib/unibuf/serializers/capnproto/struct_writer.rb +139 -0
- data/lib/unibuf/serializers/flatbuffers/binary_serializer.rb +167 -0
- data/lib/unibuf/validators/type_validator.rb +1 -1
- data/lib/unibuf/version.rb +1 -1
- data/lib/unibuf.rb +27 -0
- metadata +36 -1
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "segment_builder"
|
|
4
|
+
require_relative "pointer_encoder"
|
|
5
|
+
require_relative "struct_writer"
|
|
6
|
+
require_relative "list_writer"
|
|
7
|
+
|
|
8
|
+
module Unibuf
|
|
9
|
+
module Serializers
|
|
10
|
+
module Capnproto
|
|
11
|
+
# Serializer for Cap'n Proto binary format
|
|
12
|
+
# Coordinates segment building, pointer encoding, and data writing
|
|
13
|
+
class BinarySerializer
|
|
14
|
+
attr_reader :schema, :segment_builder
|
|
15
|
+
|
|
16
|
+
# Initialize with schema
|
|
17
|
+
# @param schema [Models::Capnproto::Schema] Cap'n Proto schema
|
|
18
|
+
def initialize(schema)
|
|
19
|
+
@schema = schema
|
|
20
|
+
@segment_builder = SegmentBuilder.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Serialize data to binary format
|
|
24
|
+
# @param data [Hash] Data to serialize
|
|
25
|
+
# @param root_type [String, nil] Root struct type name
|
|
26
|
+
# @return [String] Binary data
|
|
27
|
+
def serialize(data, root_type: nil)
|
|
28
|
+
# Determine root type from schema if not provided
|
|
29
|
+
root_type ||= @schema.structs.first&.name
|
|
30
|
+
raise SerializationError, "No root type specified" unless root_type
|
|
31
|
+
|
|
32
|
+
struct_def = @schema.find_struct(root_type)
|
|
33
|
+
unless struct_def
|
|
34
|
+
raise SerializationError,
|
|
35
|
+
"Struct type not found: #{root_type}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Allocate root pointer (1 word at position 0)
|
|
39
|
+
@segment_builder.allocate(1)
|
|
40
|
+
|
|
41
|
+
# Write root struct and get its location
|
|
42
|
+
root_location = write_struct(data, struct_def)
|
|
43
|
+
|
|
44
|
+
# Write root pointer at position 0
|
|
45
|
+
# Root pointer points to position 1 (offset = 0 from position 1)
|
|
46
|
+
pointer = PointerEncoder.encode_struct(
|
|
47
|
+
0, # offset from pointer position (1) to struct (1)
|
|
48
|
+
root_location[:data_words],
|
|
49
|
+
root_location[:pointer_words],
|
|
50
|
+
)
|
|
51
|
+
@segment_builder.write_word(0, 0, pointer)
|
|
52
|
+
|
|
53
|
+
# Build final binary
|
|
54
|
+
@segment_builder.build
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
# Write a struct
|
|
60
|
+
# @param data [Hash] Struct data
|
|
61
|
+
# @param struct_def [Models::Capnproto::StructDefinition] Struct definition
|
|
62
|
+
# @return [Hash] Location info
|
|
63
|
+
def write_struct(data, struct_def)
|
|
64
|
+
# Count data and pointer words needed
|
|
65
|
+
data_words = calculate_data_words(struct_def)
|
|
66
|
+
pointer_words = calculate_pointer_words(struct_def)
|
|
67
|
+
|
|
68
|
+
# Allocate space for struct
|
|
69
|
+
segment_id, word_offset = @segment_builder.allocate(data_words + pointer_words)
|
|
70
|
+
|
|
71
|
+
# Create struct writer
|
|
72
|
+
struct_writer = StructWriter.new(
|
|
73
|
+
@segment_builder,
|
|
74
|
+
segment_id,
|
|
75
|
+
word_offset,
|
|
76
|
+
data_words,
|
|
77
|
+
pointer_words,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Write each field
|
|
81
|
+
struct_def.fields.each do |field|
|
|
82
|
+
value = data[field.name.to_sym] || data[field.name]
|
|
83
|
+
next unless value
|
|
84
|
+
|
|
85
|
+
write_field(struct_writer, field, value, struct_def)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
{
|
|
89
|
+
segment_id: segment_id,
|
|
90
|
+
word_offset: word_offset,
|
|
91
|
+
data_words: data_words,
|
|
92
|
+
pointer_words: pointer_words,
|
|
93
|
+
}
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Write a field
|
|
97
|
+
def write_field(struct_writer, field, value, struct_def)
|
|
98
|
+
if field.primitive_type?
|
|
99
|
+
write_primitive_field(struct_writer, field, value)
|
|
100
|
+
elsif field.list_type?
|
|
101
|
+
write_list_field(struct_writer, field, value, struct_def)
|
|
102
|
+
elsif text_or_data_type?(field)
|
|
103
|
+
write_text_or_data_field(struct_writer, field, value, struct_def)
|
|
104
|
+
elsif field.user_type?
|
|
105
|
+
write_user_type_field(struct_writer, field, value, struct_def)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Write a primitive field
|
|
110
|
+
def write_primitive_field(struct_writer, field, value)
|
|
111
|
+
ordinal = field.ordinal
|
|
112
|
+
type = field.type
|
|
113
|
+
|
|
114
|
+
case type
|
|
115
|
+
when "Bool"
|
|
116
|
+
struct_writer.write_bool(ordinal / 64, ordinal % 64, value)
|
|
117
|
+
when "Int8"
|
|
118
|
+
struct_writer.write_int8(ordinal / 8, ordinal % 8, value)
|
|
119
|
+
when "UInt8"
|
|
120
|
+
struct_writer.write_uint8(ordinal / 8, ordinal % 8, value)
|
|
121
|
+
when "Int16"
|
|
122
|
+
struct_writer.write_int16(ordinal / 4, ordinal % 4, value)
|
|
123
|
+
when "UInt16"
|
|
124
|
+
struct_writer.write_uint16(ordinal / 4, ordinal % 4, value)
|
|
125
|
+
when "Int32"
|
|
126
|
+
struct_writer.write_int32(ordinal / 2, ordinal % 2, value)
|
|
127
|
+
when "UInt32"
|
|
128
|
+
struct_writer.write_uint32(ordinal / 2, ordinal % 2, value)
|
|
129
|
+
when "Int64"
|
|
130
|
+
struct_writer.write_int64(ordinal, value)
|
|
131
|
+
when "UInt64"
|
|
132
|
+
struct_writer.write_uint64(ordinal, value)
|
|
133
|
+
when "Float32"
|
|
134
|
+
struct_writer.write_float32(ordinal / 2, ordinal % 2, value)
|
|
135
|
+
when "Float64"
|
|
136
|
+
struct_writer.write_float64(ordinal, value)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Write a list field
|
|
141
|
+
def write_list_field(struct_writer, field, value, struct_def)
|
|
142
|
+
return if value.nil? || value.empty?
|
|
143
|
+
|
|
144
|
+
element_type = field.element_type
|
|
145
|
+
|
|
146
|
+
# Handle Text specially
|
|
147
|
+
if element_type == "Text"
|
|
148
|
+
write_text_field(struct_writer, field, value, struct_def)
|
|
149
|
+
return
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Determine element size
|
|
153
|
+
element_size = PointerEncoder.element_size_for_type(element_type)
|
|
154
|
+
element_count = value.length
|
|
155
|
+
|
|
156
|
+
# Allocate space for list
|
|
157
|
+
words_needed = calculate_list_words(element_size, element_count)
|
|
158
|
+
segment_id, word_offset = @segment_builder.allocate(words_needed)
|
|
159
|
+
|
|
160
|
+
# Create list writer
|
|
161
|
+
list_writer = ListWriter.new(
|
|
162
|
+
@segment_builder,
|
|
163
|
+
segment_id,
|
|
164
|
+
word_offset,
|
|
165
|
+
element_size,
|
|
166
|
+
element_count,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# Write elements
|
|
170
|
+
value.each_with_index do |elem, i|
|
|
171
|
+
if primitive_type?(element_type)
|
|
172
|
+
type_symbol = type_to_symbol(element_type)
|
|
173
|
+
list_writer.write_primitive(i, elem, type_symbol)
|
|
174
|
+
else
|
|
175
|
+
# Handle struct elements (more complex, simplified for now)
|
|
176
|
+
# Would need to recursively write structs
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Get pointer index for this field
|
|
181
|
+
pointer_index = get_pointer_index(field, struct_def)
|
|
182
|
+
|
|
183
|
+
# Calculate offset from pointer position to list
|
|
184
|
+
pointer_pos = struct_writer.pointer_position(pointer_index)
|
|
185
|
+
offset = word_offset - pointer_pos - 1
|
|
186
|
+
|
|
187
|
+
# Write list pointer
|
|
188
|
+
pointer = PointerEncoder.encode_list(offset, element_size,
|
|
189
|
+
element_count)
|
|
190
|
+
struct_writer.write_pointer(pointer_index, pointer)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Write Text or Data field
|
|
194
|
+
def write_text_or_data_field(struct_writer, field, value, struct_def)
|
|
195
|
+
if field.type == "Text"
|
|
196
|
+
write_text_field(struct_writer, field, value, struct_def)
|
|
197
|
+
else
|
|
198
|
+
# Data field - similar to text but no encoding
|
|
199
|
+
write_data_field(struct_writer, field, value, struct_def)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Write data field
|
|
204
|
+
def write_data_field(struct_writer, field, value, struct_def)
|
|
205
|
+
# Data is a byte list
|
|
206
|
+
element_count = value.bytesize
|
|
207
|
+
|
|
208
|
+
# Calculate words needed
|
|
209
|
+
words_needed = (element_count + 7) / 8
|
|
210
|
+
segment_id, word_offset = @segment_builder.allocate(words_needed)
|
|
211
|
+
|
|
212
|
+
# Create list writer
|
|
213
|
+
list_writer = ListWriter.new(
|
|
214
|
+
@segment_builder,
|
|
215
|
+
segment_id,
|
|
216
|
+
word_offset,
|
|
217
|
+
PointerEncoder::ELEMENT_SIZE_BYTE,
|
|
218
|
+
element_count,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Write data
|
|
222
|
+
list_writer.write_data(value)
|
|
223
|
+
|
|
224
|
+
# Get pointer index for this field
|
|
225
|
+
pointer_index = get_pointer_index(field, struct_def)
|
|
226
|
+
|
|
227
|
+
# Calculate offset and write pointer
|
|
228
|
+
pointer_pos = struct_writer.pointer_position(pointer_index)
|
|
229
|
+
offset = word_offset - pointer_pos - 1
|
|
230
|
+
|
|
231
|
+
pointer = PointerEncoder.encode_list(offset,
|
|
232
|
+
PointerEncoder::ELEMENT_SIZE_BYTE, element_count)
|
|
233
|
+
struct_writer.write_pointer(pointer_index, pointer)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Write text field
|
|
237
|
+
def write_text_field(struct_writer, field, value, struct_def)
|
|
238
|
+
# Text is a byte list with null terminator
|
|
239
|
+
bytes = value.bytes + [0]
|
|
240
|
+
element_count = bytes.length
|
|
241
|
+
|
|
242
|
+
# Calculate words needed
|
|
243
|
+
words_needed = (element_count + 7) / 8
|
|
244
|
+
segment_id, word_offset = @segment_builder.allocate(words_needed)
|
|
245
|
+
|
|
246
|
+
# Create list writer
|
|
247
|
+
list_writer = ListWriter.new(
|
|
248
|
+
@segment_builder,
|
|
249
|
+
segment_id,
|
|
250
|
+
word_offset,
|
|
251
|
+
PointerEncoder::ELEMENT_SIZE_BYTE,
|
|
252
|
+
element_count,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
# Write text
|
|
256
|
+
list_writer.write_text(value)
|
|
257
|
+
|
|
258
|
+
# Get pointer index for this field
|
|
259
|
+
pointer_index = get_pointer_index(field, struct_def)
|
|
260
|
+
|
|
261
|
+
# Calculate offset and write pointer
|
|
262
|
+
pointer_pos = struct_writer.pointer_position(pointer_index)
|
|
263
|
+
offset = word_offset - pointer_pos - 1
|
|
264
|
+
|
|
265
|
+
pointer = PointerEncoder.encode_list(offset,
|
|
266
|
+
PointerEncoder::ELEMENT_SIZE_BYTE, element_count)
|
|
267
|
+
struct_writer.write_pointer(pointer_index, pointer)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Write user-defined type field
|
|
271
|
+
def write_user_type_field(struct_writer, field, value, struct_def)
|
|
272
|
+
# Check if it's an enum
|
|
273
|
+
enum_def = @schema.find_enum(field.type)
|
|
274
|
+
if enum_def
|
|
275
|
+
# Enums are stored as UInt16
|
|
276
|
+
ordinal_value = if value.is_a?(String) || value.is_a?(Symbol)
|
|
277
|
+
enum_def.values[value.to_s]
|
|
278
|
+
else
|
|
279
|
+
value
|
|
280
|
+
end
|
|
281
|
+
struct_writer.write_uint16(field.ordinal / 4, field.ordinal % 4,
|
|
282
|
+
ordinal_value || 0)
|
|
283
|
+
else
|
|
284
|
+
# It's a struct - write recursively
|
|
285
|
+
nested_struct_def = @schema.find_struct(field.type)
|
|
286
|
+
return unless nested_struct_def
|
|
287
|
+
|
|
288
|
+
nested_location = write_struct(value, nested_struct_def)
|
|
289
|
+
|
|
290
|
+
# Get pointer index for this field
|
|
291
|
+
pointer_index = get_pointer_index(field, struct_def)
|
|
292
|
+
|
|
293
|
+
# Calculate offset and write pointer
|
|
294
|
+
pointer_pos = struct_writer.pointer_position(pointer_index)
|
|
295
|
+
offset = nested_location[:word_offset] - pointer_pos - 1
|
|
296
|
+
|
|
297
|
+
pointer = PointerEncoder.encode_struct(
|
|
298
|
+
offset,
|
|
299
|
+
nested_location[:data_words],
|
|
300
|
+
nested_location[:pointer_words],
|
|
301
|
+
)
|
|
302
|
+
struct_writer.write_pointer(pointer_index, pointer)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Get pointer index for a field
|
|
307
|
+
# Count non-primitive fields before this one
|
|
308
|
+
def get_pointer_index(field, struct_def)
|
|
309
|
+
struct_def.fields.take_while do |f|
|
|
310
|
+
f != field
|
|
311
|
+
end.count { |f| !f.primitive_type? }
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Helper methods
|
|
315
|
+
def calculate_data_words(struct_def)
|
|
316
|
+
# In Cap'n Proto, we need to count actual data words based on field types
|
|
317
|
+
# Group fields by their size and pack them efficiently
|
|
318
|
+
max_word = 0
|
|
319
|
+
|
|
320
|
+
struct_def.fields.each do |field|
|
|
321
|
+
next unless field.primitive_type?
|
|
322
|
+
|
|
323
|
+
word_index = case field.type
|
|
324
|
+
when "Bool"
|
|
325
|
+
field.ordinal / 64 # 64 bools per word
|
|
326
|
+
when "Int8", "UInt8"
|
|
327
|
+
field.ordinal / 8 # 8 bytes per word
|
|
328
|
+
when "Int16", "UInt16"
|
|
329
|
+
field.ordinal / 4 # 4 shorts per word
|
|
330
|
+
when "Int32", "UInt32", "Float32"
|
|
331
|
+
field.ordinal / 2 # 2 ints per word
|
|
332
|
+
when "Int64", "UInt64", "Float64"
|
|
333
|
+
field.ordinal # 1 long per word
|
|
334
|
+
else
|
|
335
|
+
0
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
max_word = [max_word, word_index].max
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
max_word + 1
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def calculate_pointer_words(struct_def)
|
|
345
|
+
# Pointer fields use separate ordinals
|
|
346
|
+
# Count fields that are NOT primitives
|
|
347
|
+
pointer_fields = struct_def.fields.reject(&:primitive_type?)
|
|
348
|
+
return 0 if pointer_fields.empty?
|
|
349
|
+
|
|
350
|
+
# Get max pointer ordinal
|
|
351
|
+
max_pointer_ordinal = pointer_fields.map(&:ordinal).max
|
|
352
|
+
max_pointer_ordinal + 1
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def calculate_list_words(element_size, count)
|
|
356
|
+
case element_size
|
|
357
|
+
when PointerEncoder::ELEMENT_SIZE_VOID
|
|
358
|
+
0
|
|
359
|
+
when PointerEncoder::ELEMENT_SIZE_BIT
|
|
360
|
+
(count + 63) / 64
|
|
361
|
+
when PointerEncoder::ELEMENT_SIZE_BYTE
|
|
362
|
+
(count + 7) / 8
|
|
363
|
+
when PointerEncoder::ELEMENT_SIZE_TWO_BYTES
|
|
364
|
+
(count + 3) / 4
|
|
365
|
+
when PointerEncoder::ELEMENT_SIZE_FOUR_BYTES
|
|
366
|
+
(count + 1) / 2
|
|
367
|
+
when PointerEncoder::ELEMENT_SIZE_EIGHT_BYTES, PointerEncoder::ELEMENT_SIZE_POINTER
|
|
368
|
+
count
|
|
369
|
+
else
|
|
370
|
+
count # Inline composite
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
def primitive_type?(type)
|
|
375
|
+
Models::Capnproto::FieldDefinition::PRIMITIVE_TYPES.include?(type)
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def type_to_symbol(type)
|
|
379
|
+
case type
|
|
380
|
+
when "Int8" then :int8
|
|
381
|
+
when "UInt8" then :uint8
|
|
382
|
+
when "Int16" then :int16
|
|
383
|
+
when "UInt16" then :uint16
|
|
384
|
+
when "Int32" then :int32
|
|
385
|
+
when "UInt32" then :uint32
|
|
386
|
+
when "Int64" then :int64
|
|
387
|
+
when "UInt64" then :uint64
|
|
388
|
+
when "Float32" then :float32
|
|
389
|
+
when "Float64" then :float64
|
|
390
|
+
when "Bool" then :bool
|
|
391
|
+
else :uint64
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Check if field is Text or Data type
|
|
396
|
+
def text_or_data_type?(field)
|
|
397
|
+
["Text", "Data"].include?(field.type)
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "segment_builder"
|
|
4
|
+
require_relative "pointer_encoder"
|
|
5
|
+
|
|
6
|
+
module Unibuf
|
|
7
|
+
module Serializers
|
|
8
|
+
module Capnproto
|
|
9
|
+
# Writer for Cap'n Proto list data
|
|
10
|
+
# Handles lists of primitives, pointers, and structs
|
|
11
|
+
class ListWriter
|
|
12
|
+
attr_reader :segment_builder, :segment_id, :word_offset, :element_size,
|
|
13
|
+
:element_count
|
|
14
|
+
|
|
15
|
+
# Initialize list writer
|
|
16
|
+
# @param segment_builder [SegmentBuilder] Segment builder
|
|
17
|
+
# @param segment_id [Integer] Segment ID
|
|
18
|
+
# @param word_offset [Integer] Word offset in segment
|
|
19
|
+
# @param element_size [Integer] Element size code
|
|
20
|
+
# @param element_count [Integer] Number of elements
|
|
21
|
+
def initialize(segment_builder, segment_id, word_offset, element_size,
|
|
22
|
+
element_count)
|
|
23
|
+
@segment_builder = segment_builder
|
|
24
|
+
@segment_id = segment_id
|
|
25
|
+
@word_offset = word_offset
|
|
26
|
+
@element_size = element_size
|
|
27
|
+
@element_count = element_count
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Write a primitive element
|
|
31
|
+
# @param index [Integer] Element index
|
|
32
|
+
# @param value [Object] Element value
|
|
33
|
+
# @param type [Symbol] Value type
|
|
34
|
+
def write_primitive(index, value, type = :uint64)
|
|
35
|
+
raise ArgumentError, "Index out of bounds" if index >= @element_count
|
|
36
|
+
|
|
37
|
+
case @element_size
|
|
38
|
+
when PointerEncoder::ELEMENT_SIZE_VOID
|
|
39
|
+
# Void - nothing to write
|
|
40
|
+
when PointerEncoder::ELEMENT_SIZE_BIT
|
|
41
|
+
write_bit(index, value)
|
|
42
|
+
when PointerEncoder::ELEMENT_SIZE_BYTE
|
|
43
|
+
write_byte(index, value, type)
|
|
44
|
+
when PointerEncoder::ELEMENT_SIZE_TWO_BYTES
|
|
45
|
+
write_two_bytes(index, value, type)
|
|
46
|
+
when PointerEncoder::ELEMENT_SIZE_FOUR_BYTES
|
|
47
|
+
write_four_bytes(index, value, type)
|
|
48
|
+
when PointerEncoder::ELEMENT_SIZE_EIGHT_BYTES
|
|
49
|
+
write_eight_bytes(index, value, type)
|
|
50
|
+
else
|
|
51
|
+
raise "Cannot write primitive to this list type"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Write a pointer element
|
|
56
|
+
# @param index [Integer] Element index
|
|
57
|
+
# @param pointer_word [Integer] Encoded pointer word
|
|
58
|
+
def write_pointer(index, pointer_word)
|
|
59
|
+
raise ArgumentError, "Index out of bounds" if index >= @element_count
|
|
60
|
+
raise "List elements are not pointers" unless @element_size == PointerEncoder::ELEMENT_SIZE_POINTER
|
|
61
|
+
|
|
62
|
+
@segment_builder.write_word(@segment_id, @word_offset + index,
|
|
63
|
+
pointer_word)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Write text (UTF-8 string) as byte list
|
|
67
|
+
# @param text [String] Text to write
|
|
68
|
+
def write_text(text)
|
|
69
|
+
raise "Not a byte list" unless @element_size == PointerEncoder::ELEMENT_SIZE_BYTE
|
|
70
|
+
|
|
71
|
+
bytes = text.bytes + [0] # Add null terminator
|
|
72
|
+
bytes.each_with_index do |byte, i|
|
|
73
|
+
write_byte(i, byte, :uint8) if i < @element_count
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Write data (binary) as byte list
|
|
78
|
+
# @param data [String] Binary data to write
|
|
79
|
+
def write_data(data)
|
|
80
|
+
raise "Not a byte list" unless @element_size == PointerEncoder::ELEMENT_SIZE_BYTE
|
|
81
|
+
|
|
82
|
+
data.bytes.each_with_index do |byte, i|
|
|
83
|
+
write_byte(i, byte, :uint8) if i < @element_count
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def write_bit(index, value)
|
|
90
|
+
word_index = index / 64
|
|
91
|
+
bit_index = index % 64
|
|
92
|
+
|
|
93
|
+
all_segments = @segment_builder.segments
|
|
94
|
+
current = if @segment_id < all_segments.length
|
|
95
|
+
all_segments[@segment_id][@word_offset + word_index] || 0
|
|
96
|
+
else
|
|
97
|
+
0
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
new_value = if value
|
|
101
|
+
current | (1 << bit_index)
|
|
102
|
+
else
|
|
103
|
+
current & ~(1 << bit_index)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
@segment_builder.write_word(@segment_id, @word_offset + word_index,
|
|
107
|
+
new_value)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def write_byte(index, value, type)
|
|
111
|
+
word_index = index / 8
|
|
112
|
+
byte_index = index % 8
|
|
113
|
+
|
|
114
|
+
all_segments = @segment_builder.segments
|
|
115
|
+
current = if @segment_id < all_segments.length
|
|
116
|
+
all_segments[@segment_id][@word_offset + word_index] || 0
|
|
117
|
+
else
|
|
118
|
+
0
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Clear the byte
|
|
122
|
+
mask = 0xFF << (byte_index * 8)
|
|
123
|
+
cleared = current & ~mask
|
|
124
|
+
|
|
125
|
+
# Set new value
|
|
126
|
+
byte_value = type == :int8 && value.negative? ? value + 256 : value
|
|
127
|
+
new_value = cleared | ((byte_value & 0xFF) << (byte_index * 8))
|
|
128
|
+
|
|
129
|
+
@segment_builder.write_word(@segment_id, @word_offset + word_index,
|
|
130
|
+
new_value)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def write_two_bytes(index, value, type)
|
|
134
|
+
word_index = index / 4
|
|
135
|
+
half_word_index = index % 4
|
|
136
|
+
|
|
137
|
+
all_segments = @segment_builder.segments
|
|
138
|
+
current = if @segment_id < all_segments.length
|
|
139
|
+
all_segments[@segment_id][@word_offset + word_index] || 0
|
|
140
|
+
else
|
|
141
|
+
0
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Clear the half-word
|
|
145
|
+
mask = 0xFFFF << (half_word_index * 16)
|
|
146
|
+
cleared = current & ~mask
|
|
147
|
+
|
|
148
|
+
# Set new value
|
|
149
|
+
short_value = type == :int16 && value.negative? ? value + 65536 : value
|
|
150
|
+
new_value = cleared | ((short_value & 0xFFFF) << (half_word_index * 16))
|
|
151
|
+
|
|
152
|
+
@segment_builder.write_word(@segment_id, @word_offset + word_index,
|
|
153
|
+
new_value)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def write_four_bytes(index, value, type)
|
|
157
|
+
word_index = index / 2
|
|
158
|
+
dword_index = index % 2
|
|
159
|
+
|
|
160
|
+
all_segments = @segment_builder.segments
|
|
161
|
+
current = if @segment_id < all_segments.length
|
|
162
|
+
all_segments[@segment_id][@word_offset + word_index] || 0
|
|
163
|
+
else
|
|
164
|
+
0
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Clear the dword
|
|
168
|
+
mask = 0xFFFFFFFF << (dword_index * 32)
|
|
169
|
+
cleared = current & ~mask
|
|
170
|
+
|
|
171
|
+
# Set new value
|
|
172
|
+
int_value = if type == :float32
|
|
173
|
+
[value].pack("f").unpack1("L")
|
|
174
|
+
else
|
|
175
|
+
type == :int32 && value.negative? ? value + 4294967296 : value
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
new_value = cleared | ((int_value & 0xFFFFFFFF) << (dword_index * 32))
|
|
179
|
+
|
|
180
|
+
@segment_builder.write_word(@segment_id, @word_offset + word_index,
|
|
181
|
+
new_value)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def write_eight_bytes(index, value, type)
|
|
185
|
+
word_value = if type == :float64
|
|
186
|
+
[value].pack("d").unpack1("Q")
|
|
187
|
+
elsif type == :int64 && value.negative?
|
|
188
|
+
value + 18446744073709551616
|
|
189
|
+
else
|
|
190
|
+
value
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
@segment_builder.write_word(@segment_id, @word_offset + index,
|
|
194
|
+
word_value & 0xFFFFFFFFFFFFFFFF)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|