red-flatbuffers 0.0.1

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.
@@ -0,0 +1,585 @@
1
+ # Copyright 2025 Sutou Kouhei <kou@clear-code.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require "fileutils"
16
+ require "pathname"
17
+
18
+ require_relative "reflection/schema"
19
+
20
+ module FlatBuffers
21
+ class Generator
22
+ module NameConvertable
23
+ private
24
+ def denamespace(name)
25
+ name.split(".")
26
+ end
27
+
28
+ def to_camel_case(name)
29
+ name.split("_").collect do |component|
30
+ component[0] = component[0].upcase
31
+ component
32
+ end.join
33
+ end
34
+
35
+ def to_snake_case(name)
36
+ snake_case = +""
37
+ previous_char = +""
38
+ name.each_char do |char|
39
+ if not snake_case.empty? and
40
+ snake_case[-1] != "_" and
41
+ char.downcase != char and
42
+ previous_char.upcase != previous_char
43
+ snake_case << "_"
44
+ end
45
+ snake_case << char
46
+ previous_char = char
47
+ end
48
+ snake_case
49
+ end
50
+
51
+ def to_upper_snake_case(name)
52
+ to_snake_case(name).upcase
53
+ end
54
+
55
+ def to_lower_snake_case(name)
56
+ to_snake_case(name).downcase
57
+ end
58
+
59
+ def to_module_name(name)
60
+ to_camel_case(name)
61
+ end
62
+
63
+ def to_class_name(name)
64
+ to_camel_case(name)
65
+ end
66
+
67
+ def to_namespaced_class_name(namespaces, name)
68
+ components = namespaces.collect {|namespace| to_module_name(namespace)}
69
+ components << to_class_name(name)
70
+ components.join("::")
71
+ end
72
+
73
+ def to_constant_name(name)
74
+ to_upper_snake_case(name)
75
+ end
76
+
77
+ def to_variable_name(name)
78
+ to_lower_snake_case(name)
79
+ end
80
+
81
+ def to_method_name(name)
82
+ to_lower_snake_case(name)
83
+ end
84
+
85
+ def to_path(name)
86
+ to_lower_snake_case(name)
87
+ end
88
+ end
89
+
90
+ include NameConvertable
91
+
92
+ class Writer
93
+ include NameConvertable
94
+
95
+ def initialize(schema, target)
96
+ @schema = schema
97
+ @target = target
98
+ @indent = +""
99
+ @requires = []
100
+ end
101
+
102
+ def add_require(path)
103
+ @requires |= [path]
104
+ end
105
+
106
+ def start(output_dir)
107
+ output_path = output_dir
108
+ components = denamespace(@target.name)
109
+ components[0..-2].each do |component|
110
+ output_path += to_path(component)
111
+ end
112
+ output_path += "#{to_path(components.last)}.rb"
113
+ FileUtils.mkdir_p(output_path.parent.to_s)
114
+ output_path.open("w") do |output|
115
+ @output = output
116
+ write_header
117
+ yield
118
+ @output = nil
119
+ end
120
+ end
121
+
122
+ def indent
123
+ @indent << " "
124
+ end
125
+
126
+ def unindent
127
+ @indent = @indent[0..-3]
128
+ end
129
+
130
+ def <<(line)
131
+ if line.empty?
132
+ @output << "\n"
133
+ else
134
+ @output << @indent << line << "\n"
135
+ end
136
+ end
137
+
138
+ def end
139
+ unindent
140
+ self << "end"
141
+ end
142
+
143
+ private
144
+ def write_header
145
+ self << "# Automatically generated. Don't modify manually."
146
+ self << "#"
147
+ self << "# Red FlatBuffers version: #{FlatBuffers::VERSION}"
148
+ declaration_file = @target.declaration_file
149
+ unless declaration_file.empty?
150
+ self << "# Declared by: #{declaration_file}"
151
+ end
152
+ root_table = @schema.root_table
153
+ if root_table
154
+ root_type = root_table.name
155
+ root_file = root_table.declaration_file
156
+ self << "# Rooting type: #{root_type} (#{root_file})"
157
+ end
158
+ self << ""
159
+ self << "require \"flatbuffers\""
160
+ @requires.each do |require|
161
+ self << "require_relative \"#{require}\""
162
+ end
163
+ self << ""
164
+ end
165
+ end
166
+
167
+ def initialize(input)
168
+ @schema = Reflection::Schema.new(input)
169
+ self.output_dir = Pathname(".")
170
+ self.outer_namespaces = nil
171
+ end
172
+
173
+ def output_dir=(dir)
174
+ dir = Pathname(dir) unless dir.is_a?(Pathname)
175
+ @output_dir = dir
176
+ end
177
+
178
+ def outer_namespaces=(namespaces)
179
+ @outer_namespaces = namespaces
180
+ end
181
+
182
+ def generate
183
+ generate_enums
184
+ generate_objects
185
+ end
186
+
187
+ private
188
+ def generate_enums
189
+ @schema.enums.each do |enum|
190
+ writer = Writer.new(@schema, enum)
191
+ writer.start(@output_dir) do
192
+ generate_enum(writer, enum)
193
+ end
194
+ end
195
+ end
196
+
197
+ def start_modules(writer, namespaces)
198
+ @outer_namespaces&.each do |ns|
199
+ writer << "module #{ns}"
200
+ writer.indent
201
+ end
202
+ namespaces.each do |ns|
203
+ writer << "module #{to_module_name(ns)}"
204
+ writer.indent
205
+ end
206
+ end
207
+
208
+ def end_modules(writer, namespaces)
209
+ namespaces.each do |ns|
210
+ writer.end
211
+ end
212
+ @outer_namespaces&.each do |ns|
213
+ writer.end
214
+ end
215
+ end
216
+
217
+ def generate_enum(writer, enum)
218
+ *namespaces, name = denamespace(enum.name)
219
+
220
+ start_modules(writer, namespaces)
221
+
222
+ if enum.union?
223
+ parent = "::FlatBuffers::Union"
224
+ else
225
+ attributes = enum.attributes
226
+ if have_attribute?(attributes, "bit_flags")
227
+ parent = "::FlatBuffers::Flags"
228
+ else
229
+ parent = "::FlatBuffers::Enum"
230
+ end
231
+ end
232
+
233
+ generate_documentation(writer, enum.documentation)
234
+ writer << "class #{to_class_name(name)} < #{parent}"
235
+ writer.indent
236
+ enum.values.each do |value|
237
+ generate_documentation(writer, value.documentation)
238
+ ruby_name = to_ruby_code(value.name)
239
+ ruby_constant_name = to_constant_name(value.name)
240
+ ruby_value = to_ruby_code(value.value)
241
+ if enum.union?
242
+ union = @schema.objects[value.union_type.index]
243
+ *union_namespaces, union_name = denamespace(union.name)
244
+ klass = "::#{to_namespaced_class_name(union_namespaces, union_name)}"
245
+ relative_union_namespaces =
246
+ resolve_namespaces(union_namespaces, namespaces)
247
+ path_components = relative_union_namespaces.collect do |ns|
248
+ to_path(ns)
249
+ end
250
+ path_components << to_path(union_name)
251
+ path = File.join(*path_components)
252
+ # NAME = register("Name", value, "ClassName", "path")
253
+ writer << ("#{ruby_constant_name} = register(" +
254
+ "#{ruby_name}, " +
255
+ "#{ruby_value}, " +
256
+ "#{to_ruby_code(klass)}, " +
257
+ "#{to_ruby_code(path)})")
258
+ else
259
+ # NAME = register("Name", value)
260
+ writer << ("#{ruby_constant_name} = " +
261
+ "register(#{ruby_name}, #{ruby_value})")
262
+ end
263
+ end
264
+ if enum.union?
265
+ writer << "\n"
266
+ writer << "private def require_table_class"
267
+ writer.indent
268
+ writer << "require_relative @require_path"
269
+ writer.end
270
+ end
271
+ writer.end
272
+
273
+ end_modules(writer, namespaces)
274
+ end
275
+
276
+ def generate_objects
277
+ @schema.objects.each do |object|
278
+ writer = Writer.new(@schema, object)
279
+ detect_object_dependencies(writer, object)
280
+ writer.start(@output_dir) do
281
+ generate_object(writer, object)
282
+ end
283
+ end
284
+ end
285
+
286
+ def detect_object_dependencies(writer, object)
287
+ *base_namespaces, _name = denamespace(object.name)
288
+ object.fields&.each do |field|
289
+ next if field.deprecated?
290
+
291
+ type = field.type
292
+ base_type = type.base_type
293
+ case base_type
294
+ when Reflection::BaseType::UTYPE,
295
+ Reflection::BaseType::BOOL,
296
+ Reflection::BaseType::BYTE,
297
+ Reflection::BaseType::UBYTE,
298
+ Reflection::BaseType::SHORT,
299
+ Reflection::BaseType::USHORT,
300
+ Reflection::BaseType::INT,
301
+ Reflection::BaseType::UINT,
302
+ Reflection::BaseType::LONG,
303
+ Reflection::BaseType::ULONG
304
+ next if type.index < 0
305
+ target = @schema.enums[type.index]
306
+ when Reflection::BaseType::OBJ
307
+ target = @schema.objects[type.index]
308
+ next if target.name == object.name
309
+ when Reflection::BaseType::ARRAY,
310
+ Reflection::BaseType::VECTOR
311
+ element_base_type = type.element
312
+ next unless element_base_type == Reflection::BaseType::OBJ
313
+ target = @schema.objects[type.index]
314
+ next if target.name == object.name
315
+ else
316
+ next
317
+ end
318
+
319
+ *namespaces, name = denamespace(target.name)
320
+ relative_namespaces = resolve_namespaces(namespaces, base_namespaces)
321
+ components = relative_namespaces.collect {|ns| to_path(ns)}
322
+ components << to_path(name)
323
+ path = File.join(*components)
324
+ writer.add_require(path)
325
+ end
326
+ end
327
+
328
+ def generate_object(writer, object)
329
+ *namespaces, name = denamespace(object.name)
330
+
331
+ start_modules(writer, namespaces)
332
+
333
+ if object.struct?
334
+ parent = "::FlatBuffers::Struct"
335
+ else
336
+ parent = "::FlatBuffers::Table"
337
+ end
338
+ generate_documentation(writer, object.documentation)
339
+ writer << "class #{to_class_name(name)} < #{parent}"
340
+ writer.indent
341
+
342
+ n_processed_fields = 0
343
+ object.fields&.each do |field|
344
+ # Skip writing deprecated fields altogether.
345
+ next if field.deprecated?
346
+
347
+ writer << "" if n_processed_fields > 0
348
+
349
+ method_name = to_method_name(field.name)
350
+ type = field.type
351
+ base_type = type.base_type
352
+ if base_type == Reflection::BaseType::BOOL
353
+ method_name = "#{method_name}?".delete_prefix("is_")
354
+ end
355
+ generate_documentation(writer, field.documentation)
356
+ writer << "def #{method_name}"
357
+ writer.indent
358
+
359
+ ruby_field_offset = to_ruby_code(field.offset)
360
+ field_offset_direct_code = "field_offset = #{ruby_field_offset}"
361
+ field_offset_virtual_code =
362
+ "field_offset = @view.unpack_virtual_offset(#{ruby_field_offset})"
363
+ return_default_virtual_code =
364
+ "return #{to_ruby_code(default_value(field))} if field_offset.zero?"
365
+ unpack_method = "@view.unpack_#{to_method_name(base_type.name)}"
366
+
367
+ case base_type
368
+ when Reflection::BaseType::UTYPE,
369
+ Reflection::BaseType::BOOL,
370
+ Reflection::BaseType::BYTE,
371
+ Reflection::BaseType::UBYTE,
372
+ Reflection::BaseType::SHORT,
373
+ Reflection::BaseType::USHORT,
374
+ Reflection::BaseType::INT,
375
+ Reflection::BaseType::UINT,
376
+ Reflection::BaseType::LONG,
377
+ Reflection::BaseType::ULONG,
378
+ Reflection::BaseType::FLOAT,
379
+ Reflection::BaseType::DOUBLE
380
+ if object.struct?
381
+ writer << field_offset_direct_code
382
+ if enum_type?(type)
383
+ writer << "enum_value = #{unpack_method}(field_offset)"
384
+ klass = resolve_class_name(type, base_type, namespaces)
385
+ writer << "#{klass}.try_convert(enum_value) || enum_value"
386
+ else
387
+ writer << "#{unpack_method}(field_offset)"
388
+ end
389
+ else
390
+ writer << field_offset_virtual_code
391
+ if enum_type?(type)
392
+ klass = resolve_class_name(type, base_type, namespaces)
393
+ writer << "if field_offset.zero?"
394
+ writer.indent
395
+ writer << "enum_value = #{to_ruby_code(default_value(field))}"
396
+ writer.unindent
397
+ writer << "else"
398
+ writer.indent
399
+ writer << "enum_value = #{unpack_method}(field_offset)"
400
+ writer.end
401
+ writer << "#{klass}.try_convert(enum_value) || enum_value"
402
+ else
403
+ writer << return_default_virtual_code
404
+ writer << ""
405
+ writer << "#{unpack_method}(field_offset)"
406
+ end
407
+ end
408
+ when Reflection::BaseType::STRING
409
+ writer << field_offset_virtual_code
410
+ writer << return_default_virtual_code
411
+ writer << ""
412
+ writer << "#{unpack_method}(field_offset)"
413
+ when Reflection::BaseType::OBJ
414
+ if object.struct?
415
+ writer << field_offset_direct_code
416
+ klass = resolve_class_name(type, base_type, namespaces)
417
+ writer << "@view.unpack_struct(#{klass}, field_offset)\n"
418
+ else
419
+ writer << field_offset_virtual_code
420
+ writer << return_default_virtual_code
421
+ writer << ""
422
+ field_object = @schema.objects[type.index]
423
+ klass = resolve_class_name(type, base_type, namespaces)
424
+ if field_object.struct?
425
+ writer << "@view.unpack_struct(#{klass}, field_offset)"
426
+ else
427
+ writer << "@view.unpack_table(#{klass}, field_offset)"
428
+ end
429
+ end
430
+ when Reflection::BaseType::UNION
431
+ writer << "type = #{to_method_name(field.name)}_type"
432
+ writer << "return nil if type.nil?"
433
+ writer << ""
434
+ writer << field_offset_virtual_code
435
+ writer << return_default_virtual_code
436
+ writer << "@view.unpack_union(type.table_class, field_offset)"
437
+ when Reflection::BaseType::ARRAY,
438
+ Reflection::BaseType::VECTOR
439
+ element_base_type = type.element
440
+ element_size = type.element_size
441
+ if element_base_type == Reflection::BaseType::OBJ
442
+ klass = resolve_class_name(type, element_base_type, namespaces)
443
+ element_object = @schema.objects[type.index]
444
+ if element_object.struct?
445
+ unpack_element_code =
446
+ "@view.unpack_struct(#{klass}, element_offset)"
447
+ else
448
+ unpack_element_code =
449
+ "@view.unpack_table(#{klass}, element_offset)"
450
+ end
451
+ else
452
+ unpack_method_name =
453
+ "unpack_#{to_method_name(element_base_type.name)}"
454
+ unpack_element_code = "@view.#{unpack_method_name}(element_offset)"
455
+ end
456
+ writer << field_offset_virtual_code
457
+ writer << return_default_virtual_code
458
+ writer << ""
459
+ writer << "element_size = #{to_ruby_code(element_size)}"
460
+ unpack_vector = "@view.unpack_vector(field_offset, element_size)"
461
+ writer << "#{unpack_vector} do |element_offset|"
462
+ writer.indent
463
+ writer << unpack_element_code
464
+ writer.end
465
+ end
466
+ writer.end
467
+
468
+ n_processed_fields += 1
469
+ end
470
+
471
+ writer.end # class
472
+
473
+ end_modules(writer, namespaces)
474
+ end
475
+
476
+ def generate_documentation(writer, documentation)
477
+ documentation&.each do |line|
478
+ writer << "\##{line}"
479
+ end
480
+ end
481
+
482
+ def have_attribute?(attributes, key)
483
+ return false if attributes.nil?
484
+ attributes.any? do |attribute|
485
+ attribute.key == key
486
+ end
487
+ end
488
+
489
+ def to_ruby_code(value)
490
+ case value
491
+ when String
492
+ value.dump
493
+ when Float
494
+ if value.nan?
495
+ "Float::NAN"
496
+ elsif value.infinite?
497
+ if value > 0
498
+ "Float::INFINITY"
499
+ else
500
+ "-Float::INFINITY"
501
+ end
502
+ else
503
+ value.to_s
504
+ end
505
+ when nil
506
+ "nil"
507
+ else
508
+ value.to_s
509
+ end
510
+ end
511
+
512
+ def default_value(field)
513
+ base_type = field.type.base_type
514
+ case base_type
515
+ when Reflection::BaseType::FLOAT,
516
+ Reflection::BaseType::DOUBLE
517
+ field.default_real
518
+ when Reflection::BaseType::BOOL
519
+ not field.default_integer.zero?
520
+ when Reflection::BaseType::UTYPE,
521
+ Reflection::BaseType::BYTE,
522
+ Reflection::BaseType::UBYTE,
523
+ Reflection::BaseType::SHORT,
524
+ Reflection::BaseType::USHORT,
525
+ Reflection::BaseType::INT,
526
+ Reflection::BaseType::UINT,
527
+ Reflection::BaseType::LONG,
528
+ Reflection::BaseType::ULONG
529
+ field.default_integer
530
+ else
531
+ nil
532
+ end
533
+ end
534
+
535
+ def scalar_type?(base_type)
536
+ Reflection::BaseType::UTYPE.value <= base_type.value and
537
+ base_type.value >= Reflection::BaseType::DOUBLE.value
538
+ end
539
+
540
+ def integer_type?(base_type)
541
+ Reflection::BaseType::UTYPE.value <= base_type.value and
542
+ base_type.value <= Reflection::BaseType::ULONG.value
543
+ end
544
+
545
+ def enum_type?(type)
546
+ integer_type?(type.base_type) and type.index >= 0
547
+ end
548
+
549
+ def resolve_namespaces(namespaces, base_namespaces)
550
+ resolved_namespaces = namespaces.dup
551
+ base_namespaces.size.times do |i|
552
+ base_namespace = base_namespaces[i]
553
+ if namespaces.empty? or namespaces[0] != base_namespace
554
+ i.step(base_namespaces.size - 1) do
555
+ resolved_namespaces.unshift("..")
556
+ end
557
+ break
558
+ end
559
+ resolved_namespaces.shift
560
+ end
561
+ resolved_namespaces
562
+ end
563
+
564
+ def resolve_class_name(type,
565
+ base_type,
566
+ base_namespaces)
567
+ if base_type == Reflection::BaseType::OBJ
568
+ target = @schema.objects[type.index]
569
+ else
570
+ target = @schema.enums[type.index]
571
+ end
572
+ *namespaces, name = denamespace(target.name)
573
+ relative_namespaces = resolve_namespaces(namespaces, base_namespaces)
574
+ in_same_namespace =
575
+ (relative_namespaces.empty? or relative_namespaces[0] != "..")
576
+ if in_same_namespace
577
+ # We can use relative hierarchy
578
+ to_namespaced_class_name(relative_namespaces, name)
579
+ else
580
+ # We need to use absolute hierarchy
581
+ "::#{to_namespaced_class_name(namespaces, name)}"
582
+ end
583
+ end
584
+ end
585
+ end
@@ -0,0 +1,41 @@
1
+ # Copyright 2025 Sutou Kouhei <kou@clear-code.com>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module FlatBuffers
16
+ module Inspectable
17
+ def inspect
18
+ inspected = +"<#{self.class}:"
19
+ public_methods(false).each do |name|
20
+ next unless method(name).arity.zero?
21
+ inspected << " #{name}=#{__send__(name).inspect}"
22
+ end
23
+ inspected << ">"
24
+ inspected
25
+ end
26
+
27
+ def pretty_print(q)
28
+ q.object_group(self) do
29
+ targets = public_methods(false).select do |name|
30
+ method(name).arity.zero?
31
+ end
32
+ q.seplist(targets, lambda {q.text(",")}) do |name|
33
+ q.breakable
34
+ q.text(name.to_s)
35
+ q.text("=")
36
+ q.pp(__send__(name))
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,19 @@
1
+ # Automatically generated. Don't modify manually.
2
+ #
3
+ # Red FlatBuffers version: 0.0.1
4
+ # Declared by: //reflection.fbs
5
+ # Rooting type: reflection.Schema (//reflection.fbs)
6
+
7
+ require "flatbuffers"
8
+
9
+ module FlatBuffers
10
+ module Reflection
11
+ # New schema language features that are not supported by old code generators.
12
+ class AdvancedFeatures < ::FlatBuffers::Flags
13
+ ADVANCED_ARRAY_FEATURES = register("AdvancedArrayFeatures", 1)
14
+ ADVANCED_UNION_FEATURES = register("AdvancedUnionFeatures", 2)
15
+ OPTIONAL_SCALARS = register("OptionalScalars", 4)
16
+ DEFAULT_VECTORS_AND_STRINGS = register("DefaultVectorsAndStrings", 8)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,34 @@
1
+ # Automatically generated. Don't modify manually.
2
+ #
3
+ # Red FlatBuffers version: 0.0.1
4
+ # Declared by: //reflection.fbs
5
+ # Rooting type: reflection.Schema (//reflection.fbs)
6
+
7
+ require "flatbuffers"
8
+
9
+ module FlatBuffers
10
+ module Reflection
11
+ class BaseType < ::FlatBuffers::Enum
12
+ NONE = register("None", 0)
13
+ UTYPE = register("UType", 1)
14
+ BOOL = register("Bool", 2)
15
+ BYTE = register("Byte", 3)
16
+ UBYTE = register("UByte", 4)
17
+ SHORT = register("Short", 5)
18
+ USHORT = register("UShort", 6)
19
+ INT = register("Int", 7)
20
+ UINT = register("UInt", 8)
21
+ LONG = register("Long", 9)
22
+ ULONG = register("ULong", 10)
23
+ FLOAT = register("Float", 11)
24
+ DOUBLE = register("Double", 12)
25
+ STRING = register("String", 13)
26
+ VECTOR = register("Vector", 14)
27
+ OBJ = register("Obj", 15)
28
+ UNION = register("Union", 16)
29
+ ARRAY = register("Array", 17)
30
+ VECTOR64 = register("Vector64", 18)
31
+ MAX_BASE_TYPE = register("MaxBaseType", 19)
32
+ end
33
+ end
34
+ end