unibuf 0.1.1 → 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 +170 -200
- data/CODE_OF_CONDUCT.md +132 -0
- data/README.adoc +306 -114
- 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/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/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/version.rb +1 -1
- data/lib/unibuf.rb +27 -0
- metadata +36 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unibuf
|
|
4
|
+
module Models
|
|
5
|
+
module Flatbuffers
|
|
6
|
+
# Represents a FlatBuffers schema (.fbs file)
|
|
7
|
+
class Schema
|
|
8
|
+
attr_reader :namespace, :includes, :tables, :structs, :enums, :unions,
|
|
9
|
+
:root_type, :file_identifier, :file_extension, :attributes
|
|
10
|
+
|
|
11
|
+
def initialize(attributes = {})
|
|
12
|
+
@namespace = attributes[:namespace] || attributes["namespace"]
|
|
13
|
+
@includes = Array(attributes[:includes] || attributes["includes"])
|
|
14
|
+
@tables = Array(attributes[:tables] || attributes["tables"])
|
|
15
|
+
@structs = Array(attributes[:structs] || attributes["structs"])
|
|
16
|
+
@enums = Array(attributes[:enums] || attributes["enums"])
|
|
17
|
+
@unions = Array(attributes[:unions] || attributes["unions"])
|
|
18
|
+
@root_type = attributes[:root_type] || attributes["root_type"]
|
|
19
|
+
@file_identifier = attributes[:file_identifier] || attributes["file_identifier"]
|
|
20
|
+
@file_extension = attributes[:file_extension] || attributes["file_extension"]
|
|
21
|
+
@attributes = Array(attributes[:attributes] || attributes["attributes"])
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Queries
|
|
25
|
+
def find_table(name)
|
|
26
|
+
tables.find { |t| t.name == name }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def find_struct(name)
|
|
30
|
+
structs.find { |s| s.name == name }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def find_enum(name)
|
|
34
|
+
enums.find { |e| e.name == name }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def find_union(name)
|
|
38
|
+
unions.find { |u| u.name == name }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def find_type(name)
|
|
42
|
+
find_table(name) || find_struct(name) || find_enum(name) || find_union(name)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def table_names
|
|
46
|
+
tables.map(&:name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def struct_names
|
|
50
|
+
structs.map(&:name)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def enum_names
|
|
54
|
+
enums.map(&:name)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def union_names
|
|
58
|
+
unions.map(&:name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Validation
|
|
62
|
+
def valid?
|
|
63
|
+
validate!
|
|
64
|
+
true
|
|
65
|
+
rescue ValidationError
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def validate!
|
|
70
|
+
raise ValidationError, "Root type required" unless root_type
|
|
71
|
+
|
|
72
|
+
unless find_table(root_type)
|
|
73
|
+
raise ValidationError,
|
|
74
|
+
"Root type '#{root_type}' not found"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
tables.each(&:validate!)
|
|
78
|
+
structs.each(&:validate!)
|
|
79
|
+
enums.each(&:validate!)
|
|
80
|
+
unions.each(&:validate!)
|
|
81
|
+
|
|
82
|
+
true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def to_h
|
|
86
|
+
{
|
|
87
|
+
namespace: namespace,
|
|
88
|
+
includes: includes,
|
|
89
|
+
tables: tables.map(&:to_h),
|
|
90
|
+
structs: structs.map(&:to_h),
|
|
91
|
+
enums: enums.map(&:to_h),
|
|
92
|
+
unions: unions.map(&:to_h),
|
|
93
|
+
root_type: root_type,
|
|
94
|
+
file_identifier: file_identifier,
|
|
95
|
+
file_extension: file_extension,
|
|
96
|
+
attributes: attributes,
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unibuf
|
|
4
|
+
module Models
|
|
5
|
+
module Flatbuffers
|
|
6
|
+
# Represents a FlatBuffers struct definition
|
|
7
|
+
# Structs are fixed-size, stored inline (no vtable)
|
|
8
|
+
class StructDefinition
|
|
9
|
+
attr_reader :name, :fields, :metadata
|
|
10
|
+
|
|
11
|
+
def initialize(attributes = {})
|
|
12
|
+
@name = attributes[:name] || attributes["name"]
|
|
13
|
+
@fields = Array(attributes[:fields] || attributes["fields"])
|
|
14
|
+
@metadata = attributes[:metadata] || attributes["metadata"] || {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Queries
|
|
18
|
+
def find_field(field_name)
|
|
19
|
+
fields.find { |f| f.name == field_name }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def field_names
|
|
23
|
+
fields.map(&:name)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Classification
|
|
27
|
+
def fixed_size?
|
|
28
|
+
# Structs are always fixed size
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Validation
|
|
33
|
+
def valid?
|
|
34
|
+
validate!
|
|
35
|
+
true
|
|
36
|
+
rescue ValidationError
|
|
37
|
+
false
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def validate!
|
|
41
|
+
raise ValidationError, "Struct name required" unless name
|
|
42
|
+
|
|
43
|
+
if fields.empty?
|
|
44
|
+
raise ValidationError,
|
|
45
|
+
"Struct must have at least one field"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Structs cannot contain vectors or other non-scalar types
|
|
49
|
+
fields.each do |field|
|
|
50
|
+
field.validate!
|
|
51
|
+
if field.vector?
|
|
52
|
+
raise ValidationError,
|
|
53
|
+
"Struct '#{name}' field '#{field.name}' cannot be a vector"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def to_h
|
|
61
|
+
{
|
|
62
|
+
name: name,
|
|
63
|
+
fields: fields.map(&:to_h),
|
|
64
|
+
metadata: metadata,
|
|
65
|
+
}
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unibuf
|
|
4
|
+
module Models
|
|
5
|
+
module Flatbuffers
|
|
6
|
+
# Represents a FlatBuffers table definition
|
|
7
|
+
class TableDefinition
|
|
8
|
+
attr_reader :name, :fields, :metadata
|
|
9
|
+
|
|
10
|
+
def initialize(attributes = {})
|
|
11
|
+
@name = attributes[:name] || attributes["name"]
|
|
12
|
+
@fields = Array(attributes[:fields] || attributes["fields"])
|
|
13
|
+
@metadata = attributes[:metadata] || attributes["metadata"] || {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Queries
|
|
17
|
+
def find_field(field_name)
|
|
18
|
+
fields.find { |f| f.name == field_name }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def field_names
|
|
22
|
+
fields.map(&:name)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def has_metadata?(key)
|
|
26
|
+
metadata.key?(key)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get_metadata(key)
|
|
30
|
+
metadata[key]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Classification
|
|
34
|
+
def has_vectors?
|
|
35
|
+
fields.any?(&:vector?)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def has_nested_tables?
|
|
39
|
+
fields.any? { |f| f.type_kind == :table }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Validation
|
|
43
|
+
def valid?
|
|
44
|
+
validate!
|
|
45
|
+
true
|
|
46
|
+
rescue ValidationError
|
|
47
|
+
false
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def validate!
|
|
51
|
+
raise ValidationError, "Table name required" unless name
|
|
52
|
+
|
|
53
|
+
if fields.empty?
|
|
54
|
+
raise ValidationError,
|
|
55
|
+
"Table must have at least one field"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
fields.each(&:validate!)
|
|
59
|
+
|
|
60
|
+
true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def to_h
|
|
64
|
+
{
|
|
65
|
+
name: name,
|
|
66
|
+
fields: fields.map(&:to_h),
|
|
67
|
+
metadata: metadata,
|
|
68
|
+
}
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Unibuf
|
|
4
|
+
module Models
|
|
5
|
+
module Flatbuffers
|
|
6
|
+
# Represents a FlatBuffers union definition
|
|
7
|
+
# Unions represent a choice between multiple table types
|
|
8
|
+
class UnionDefinition
|
|
9
|
+
attr_reader :name, :types, :metadata
|
|
10
|
+
|
|
11
|
+
def initialize(attributes = {})
|
|
12
|
+
@name = attributes[:name] || attributes["name"]
|
|
13
|
+
@types = Array(attributes[:types] || attributes["types"])
|
|
14
|
+
@metadata = attributes[:metadata] || attributes["metadata"] || {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Queries
|
|
18
|
+
def includes_type?(type_name)
|
|
19
|
+
types.include?(type_name)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def type_count
|
|
23
|
+
types.size
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Validation
|
|
27
|
+
def valid?
|
|
28
|
+
validate!
|
|
29
|
+
true
|
|
30
|
+
rescue ValidationError
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def validate!
|
|
35
|
+
raise ValidationError, "Union name required" unless name
|
|
36
|
+
|
|
37
|
+
if types.empty?
|
|
38
|
+
raise ValidationError,
|
|
39
|
+
"Union must have at least one type"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Check for duplicate types
|
|
43
|
+
if types.uniq.size != types.size
|
|
44
|
+
raise ValidationError, "Union '#{name}' has duplicate types"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def to_h
|
|
51
|
+
{
|
|
52
|
+
name: name,
|
|
53
|
+
types: types,
|
|
54
|
+
metadata: metadata,
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -182,6 +182,16 @@ module Unibuf
|
|
|
182
182
|
lines.join("\n")
|
|
183
183
|
end
|
|
184
184
|
|
|
185
|
+
# Serialize to binary Protocol Buffer format
|
|
186
|
+
# @param schema [Models::Schema] The schema defining the message structure
|
|
187
|
+
# @param message_type [String] The message type name from schema
|
|
188
|
+
# @return [String] Binary data
|
|
189
|
+
def to_binary(schema:, message_type: nil)
|
|
190
|
+
require_relative "../serializers/binary_serializer"
|
|
191
|
+
serializer = Serializers::BinarySerializer.new(schema)
|
|
192
|
+
serializer.serialize(self, message_type: message_type)
|
|
193
|
+
end
|
|
194
|
+
|
|
185
195
|
# Comparison
|
|
186
196
|
def ==(other)
|
|
187
197
|
return false unless other.is_a?(Message)
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "segment_reader"
|
|
4
|
+
require_relative "pointer_decoder"
|
|
5
|
+
require_relative "struct_reader"
|
|
6
|
+
require_relative "list_reader"
|
|
7
|
+
|
|
8
|
+
module Unibuf
|
|
9
|
+
module Parsers
|
|
10
|
+
module Capnproto
|
|
11
|
+
# Parser for Cap'n Proto binary format
|
|
12
|
+
# Coordinates segment reading, pointer following, and data extraction
|
|
13
|
+
class BinaryParser
|
|
14
|
+
attr_reader :schema, :segment_reader
|
|
15
|
+
|
|
16
|
+
# Initialize with schema
|
|
17
|
+
# @param schema [Models::Capnproto::Schema] Cap'n Proto schema
|
|
18
|
+
def initialize(schema)
|
|
19
|
+
@schema = schema
|
|
20
|
+
@segment_reader = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Parse binary data
|
|
24
|
+
# @param data [String] Binary data
|
|
25
|
+
# @param root_type [String, nil] Root struct type name
|
|
26
|
+
# @return [Hash] Parsed data
|
|
27
|
+
def parse(data, root_type: nil)
|
|
28
|
+
@segment_reader = SegmentReader.new(data)
|
|
29
|
+
|
|
30
|
+
# Root object is at segment 0, word 0
|
|
31
|
+
# First word is a pointer to the root struct
|
|
32
|
+
root_pointer_word = @segment_reader.read_word(0, 0)
|
|
33
|
+
root_pointer = PointerDecoder.decode(root_pointer_word)
|
|
34
|
+
|
|
35
|
+
unless root_pointer[:type] == :struct
|
|
36
|
+
raise ParseError,
|
|
37
|
+
"Invalid root pointer"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Follow pointer to root struct
|
|
41
|
+
root_struct_offset = 1 + root_pointer[:offset]
|
|
42
|
+
root_struct = StructReader.new(
|
|
43
|
+
@segment_reader,
|
|
44
|
+
0,
|
|
45
|
+
root_struct_offset,
|
|
46
|
+
root_pointer[:data_words],
|
|
47
|
+
root_pointer[:pointer_words],
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Determine root type from schema if not provided
|
|
51
|
+
root_type ||= @schema.structs.first&.name
|
|
52
|
+
raise ParseError, "No root type specified" unless root_type
|
|
53
|
+
|
|
54
|
+
struct_def = @schema.find_struct(root_type)
|
|
55
|
+
unless struct_def
|
|
56
|
+
raise ParseError,
|
|
57
|
+
"Struct type not found: #{root_type}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
parse_struct(root_struct, struct_def)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# Parse a struct according to its definition
|
|
66
|
+
# @param struct_reader [StructReader] Struct reader
|
|
67
|
+
# @param struct_def [Models::Capnproto::StructDefinition] Struct definition
|
|
68
|
+
# @return [Hash] Parsed data
|
|
69
|
+
def parse_struct(struct_reader, struct_def)
|
|
70
|
+
result = {}
|
|
71
|
+
|
|
72
|
+
struct_def.fields.each do |field|
|
|
73
|
+
result[field.name.to_sym] =
|
|
74
|
+
parse_field(struct_reader, field, struct_def)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
result
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Parse a field
|
|
81
|
+
# @param struct_reader [StructReader] Struct reader
|
|
82
|
+
# @param field [Models::Capnproto::FieldDefinition] Field definition
|
|
83
|
+
# @param struct_def [Models::Capnproto::StructDefinition] Parent struct definition
|
|
84
|
+
# @return [Object] Field value
|
|
85
|
+
def parse_field(struct_reader, field, struct_def)
|
|
86
|
+
if field.primitive_type?
|
|
87
|
+
parse_primitive_field(struct_reader, field)
|
|
88
|
+
elsif field.list_type?
|
|
89
|
+
parse_list_field(struct_reader, field, struct_def)
|
|
90
|
+
elsif text_or_data_type?(field)
|
|
91
|
+
parse_text_or_data_field(struct_reader, field, struct_def)
|
|
92
|
+
elsif field.user_type?
|
|
93
|
+
parse_user_type_field(struct_reader, field, struct_def)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Parse a primitive field
|
|
98
|
+
def parse_primitive_field(struct_reader, field)
|
|
99
|
+
ordinal = field.ordinal
|
|
100
|
+
type = field.type
|
|
101
|
+
|
|
102
|
+
# Calculate word and offset based on type
|
|
103
|
+
case type
|
|
104
|
+
when "Bool"
|
|
105
|
+
struct_reader.read_bool(ordinal / 64, ordinal % 64)
|
|
106
|
+
when "Int8"
|
|
107
|
+
struct_reader.read_int8(ordinal / 8, ordinal % 8)
|
|
108
|
+
when "UInt8"
|
|
109
|
+
struct_reader.read_uint8(ordinal / 8, ordinal % 8)
|
|
110
|
+
when "Int16"
|
|
111
|
+
struct_reader.read_int16(ordinal / 4, ordinal % 4)
|
|
112
|
+
when "UInt16"
|
|
113
|
+
struct_reader.read_uint16(ordinal / 4, ordinal % 4)
|
|
114
|
+
when "Int32"
|
|
115
|
+
struct_reader.read_int32(ordinal / 2, ordinal % 2)
|
|
116
|
+
when "UInt32"
|
|
117
|
+
struct_reader.read_uint32(ordinal / 2, ordinal % 2)
|
|
118
|
+
when "Int64"
|
|
119
|
+
struct_reader.read_int64(ordinal)
|
|
120
|
+
when "UInt64"
|
|
121
|
+
struct_reader.read_uint64(ordinal)
|
|
122
|
+
when "Float32"
|
|
123
|
+
struct_reader.read_float32(ordinal / 2, ordinal % 2)
|
|
124
|
+
when "Float64"
|
|
125
|
+
struct_reader.read_float64(ordinal)
|
|
126
|
+
when "Void"
|
|
127
|
+
nil
|
|
128
|
+
else
|
|
129
|
+
field.default_value
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Parse a list field
|
|
134
|
+
def parse_list_field(struct_reader, field, struct_def)
|
|
135
|
+
# Get pointer index - count non-primitive fields before this one
|
|
136
|
+
pointer_index = get_pointer_index(field, struct_def)
|
|
137
|
+
|
|
138
|
+
target = struct_reader.follow_pointer(pointer_index)
|
|
139
|
+
return nil unless target && target[:type] == :list
|
|
140
|
+
|
|
141
|
+
list_reader = ListReader.new(
|
|
142
|
+
@segment_reader,
|
|
143
|
+
target[:segment_id],
|
|
144
|
+
target[:word_offset],
|
|
145
|
+
target[:element_size],
|
|
146
|
+
target[:element_count],
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
element_type = field.element_type
|
|
150
|
+
|
|
151
|
+
# Check if element is Text or Data
|
|
152
|
+
if element_type == "Text"
|
|
153
|
+
return list_reader.read_text
|
|
154
|
+
elsif element_type == "Data"
|
|
155
|
+
return list_reader.read_data
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Parse list elements
|
|
159
|
+
(0...list_reader.length).map do |i|
|
|
160
|
+
if primitive_type?(element_type)
|
|
161
|
+
type_symbol = type_to_symbol(element_type)
|
|
162
|
+
list_reader.read_primitive(i, type_symbol)
|
|
163
|
+
else
|
|
164
|
+
# Struct element
|
|
165
|
+
element_struct_def = @schema.find_struct(element_type)
|
|
166
|
+
if element_struct_def
|
|
167
|
+
element_struct = list_reader.read_struct(i)
|
|
168
|
+
parse_struct(element_struct, element_struct_def)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Parse a user-defined type field (struct, enum, etc.)
|
|
175
|
+
def parse_user_type_field(struct_reader, field, struct_def)
|
|
176
|
+
# Check if it's an enum
|
|
177
|
+
enum_def = @schema.find_enum(field.type)
|
|
178
|
+
if enum_def
|
|
179
|
+
# Enums are stored as UInt16 in data section
|
|
180
|
+
value = struct_reader.read_uint16(field.ordinal / 4,
|
|
181
|
+
field.ordinal % 4)
|
|
182
|
+
# Find enum name by value
|
|
183
|
+
enum_def.find_name_by_ordinal(value) || value
|
|
184
|
+
else
|
|
185
|
+
# It's a struct - use pointer index
|
|
186
|
+
pointer_index = get_pointer_index(field, struct_def)
|
|
187
|
+
|
|
188
|
+
target = struct_reader.follow_pointer(pointer_index)
|
|
189
|
+
return nil unless target && target[:type] == :struct
|
|
190
|
+
|
|
191
|
+
nested_struct = StructReader.new(
|
|
192
|
+
@segment_reader,
|
|
193
|
+
target[:segment_id],
|
|
194
|
+
target[:word_offset],
|
|
195
|
+
target[:data_words],
|
|
196
|
+
target[:pointer_words],
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
nested_struct_def = @schema.find_struct(field.type)
|
|
200
|
+
return nil unless nested_struct_def
|
|
201
|
+
|
|
202
|
+
parse_struct(nested_struct, nested_struct_def)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Parse Text or Data field (special pointer types)
|
|
207
|
+
def parse_text_or_data_field(struct_reader, field, struct_def)
|
|
208
|
+
# Get pointer index
|
|
209
|
+
pointer_index = get_pointer_index(field, struct_def)
|
|
210
|
+
|
|
211
|
+
target = struct_reader.follow_pointer(pointer_index)
|
|
212
|
+
return nil unless target && target[:type] == :list
|
|
213
|
+
|
|
214
|
+
list_reader = ListReader.new(
|
|
215
|
+
@segment_reader,
|
|
216
|
+
target[:segment_id],
|
|
217
|
+
target[:word_offset],
|
|
218
|
+
target[:element_size],
|
|
219
|
+
target[:element_count],
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if field.type == "Text"
|
|
223
|
+
list_reader.read_text
|
|
224
|
+
else
|
|
225
|
+
list_reader.read_data
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Get pointer index for a field
|
|
230
|
+
# Count non-primitive fields before this one
|
|
231
|
+
def get_pointer_index(field, struct_def)
|
|
232
|
+
struct_def.fields.take_while do |f|
|
|
233
|
+
f != field
|
|
234
|
+
end.count { |f| !f.primitive_type? }
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Check if field is Text or Data type
|
|
238
|
+
def text_or_data_type?(field)
|
|
239
|
+
["Text", "Data"].include?(field.type)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Check if type is primitive
|
|
243
|
+
def primitive_type?(type)
|
|
244
|
+
Models::Capnproto::FieldDefinition::PRIMITIVE_TYPES.include?(type)
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Convert type string to symbol for list reading
|
|
248
|
+
def type_to_symbol(type)
|
|
249
|
+
case type
|
|
250
|
+
when "Int8" then :int8
|
|
251
|
+
when "UInt8" then :uint8
|
|
252
|
+
when "Int16" then :int16
|
|
253
|
+
when "UInt16" then :uint16
|
|
254
|
+
when "Int32" then :int32
|
|
255
|
+
when "UInt32" then :uint32
|
|
256
|
+
when "Int64" then :int64
|
|
257
|
+
when "UInt64" then :uint64
|
|
258
|
+
when "Float32" then :float32
|
|
259
|
+
when "Float64" then :float64
|
|
260
|
+
when "Bool" then :bool
|
|
261
|
+
else :uint64
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|