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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +170 -200
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/README.adoc +306 -114
  5. data/docs/CAPNPROTO.adoc +436 -0
  6. data/docs/FLATBUFFERS.adoc +430 -0
  7. data/docs/PROTOBUF.adoc +515 -0
  8. data/docs/TXTPROTO.adoc +369 -0
  9. data/lib/unibuf/commands/convert.rb +60 -2
  10. data/lib/unibuf/commands/schema.rb +68 -11
  11. data/lib/unibuf/errors.rb +23 -26
  12. data/lib/unibuf/models/capnproto/enum_definition.rb +72 -0
  13. data/lib/unibuf/models/capnproto/field_definition.rb +81 -0
  14. data/lib/unibuf/models/capnproto/interface_definition.rb +70 -0
  15. data/lib/unibuf/models/capnproto/method_definition.rb +81 -0
  16. data/lib/unibuf/models/capnproto/schema.rb +84 -0
  17. data/lib/unibuf/models/capnproto/struct_definition.rb +96 -0
  18. data/lib/unibuf/models/capnproto/union_definition.rb +62 -0
  19. data/lib/unibuf/models/flatbuffers/enum_definition.rb +69 -0
  20. data/lib/unibuf/models/flatbuffers/field_definition.rb +88 -0
  21. data/lib/unibuf/models/flatbuffers/schema.rb +102 -0
  22. data/lib/unibuf/models/flatbuffers/struct_definition.rb +70 -0
  23. data/lib/unibuf/models/flatbuffers/table_definition.rb +73 -0
  24. data/lib/unibuf/models/flatbuffers/union_definition.rb +60 -0
  25. data/lib/unibuf/models/message.rb +10 -0
  26. data/lib/unibuf/parsers/capnproto/binary_parser.rb +267 -0
  27. data/lib/unibuf/parsers/capnproto/grammar.rb +272 -0
  28. data/lib/unibuf/parsers/capnproto/list_reader.rb +208 -0
  29. data/lib/unibuf/parsers/capnproto/pointer_decoder.rb +163 -0
  30. data/lib/unibuf/parsers/capnproto/processor.rb +348 -0
  31. data/lib/unibuf/parsers/capnproto/segment_reader.rb +131 -0
  32. data/lib/unibuf/parsers/capnproto/struct_reader.rb +199 -0
  33. data/lib/unibuf/parsers/flatbuffers/binary_parser.rb +325 -0
  34. data/lib/unibuf/parsers/flatbuffers/grammar.rb +235 -0
  35. data/lib/unibuf/parsers/flatbuffers/processor.rb +299 -0
  36. data/lib/unibuf/serializers/binary_serializer.rb +218 -0
  37. data/lib/unibuf/serializers/capnproto/binary_serializer.rb +402 -0
  38. data/lib/unibuf/serializers/capnproto/list_writer.rb +199 -0
  39. data/lib/unibuf/serializers/capnproto/pointer_encoder.rb +118 -0
  40. data/lib/unibuf/serializers/capnproto/segment_builder.rb +124 -0
  41. data/lib/unibuf/serializers/capnproto/struct_writer.rb +139 -0
  42. data/lib/unibuf/serializers/flatbuffers/binary_serializer.rb +167 -0
  43. data/lib/unibuf/version.rb +1 -1
  44. data/lib/unibuf.rb +27 -0
  45. 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