capnp 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.
- checksums.yaml +7 -0
- data/bin/capnpc-ruby +15 -0
- data/lib/capnp/generator/generator.rb +568 -0
- data/lib/capnp/generator/schema.capnp.rb +1327 -0
- data/lib/capnp/generator.rb +4 -0
- data/lib/capnp/runtime/buffer/buffer.rb +62 -0
- data/lib/capnp/runtime/buffer/io_buffer.rb +85 -0
- data/lib/capnp/runtime/buffer/sliceable_buffer.rb +16 -0
- data/lib/capnp/runtime/buffer/string_buffer.rb +88 -0
- data/lib/capnp/runtime/list/buffer/data.rb +19 -0
- data/lib/capnp/runtime/list/buffer/list.rb +100 -0
- data/lib/capnp/runtime/list/buffer/numeric.rb +103 -0
- data/lib/capnp/runtime/list/buffer/string.rb +19 -0
- data/lib/capnp/runtime/list/buffer/struct.rb +47 -0
- data/lib/capnp/runtime/list/list.rb +42 -0
- data/lib/capnp/runtime/list/object/list.rb +26 -0
- data/lib/capnp/runtime/list/object/string.rb +26 -0
- data/lib/capnp/runtime/list/string.rb +22 -0
- data/lib/capnp/runtime/message/flat_message.rb +21 -0
- data/lib/capnp/runtime/message/message.rb +62 -0
- data/lib/capnp/runtime/message/stream_message.rb +50 -0
- data/lib/capnp/runtime/reference.rb +100 -0
- data/lib/capnp/runtime/segment.rb +89 -0
- data/lib/capnp/runtime/struct.rb +159 -0
- data/lib/capnp/runtime.rb +3 -0
- data/lib/capnp/version.rb +5 -0
- data/lib/capnp.rb +14 -0
- metadata +200 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 71ef797c99d9b126f104b4d4ea36c776ca8dd870d6fc721314710e8e74b7f133
|
4
|
+
data.tar.gz: 96cba73c43bd3d9656f88c21a9c7b745bac8cdeb66cee4ba84ad90ba63bdf573
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 284eefe7ac8c07d0eb9c390d2efdeef4d26464a095b13d87aa568c8da35c5ffb228d2a33e078769fe526861782de066281ca5d6405e30d63657a64416fd3b805
|
7
|
+
data.tar.gz: 9726a5775467dd15e6d2455de023d5d7743a59c6475564b63e3903edb130161883bc6cdc3825820fa969db93477b3ec90502909c52168cfad94fb679eb635ffd
|
data/bin/capnpc-ruby
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# typed: strict
|
3
|
+
|
4
|
+
require "capnp"
|
5
|
+
require "capnp/generator"
|
6
|
+
|
7
|
+
begin
|
8
|
+
buffer = Capnp::IOBuffer.new(IO::Buffer.for(STDIN.read))
|
9
|
+
message = Capnp::StreamMessage.new(buffer)
|
10
|
+
generator = Capnp::Generator.new(message.root)
|
11
|
+
generator.generate
|
12
|
+
rescue => e
|
13
|
+
warn "#{e.class}: #{e.message}"
|
14
|
+
e.backtrace.to_a.reject { |line| line.include?("/gems/sorbet-runtime-") }.each { |line| warn(line) }
|
15
|
+
end
|
@@ -0,0 +1,568 @@
|
|
1
|
+
# typed: strict
|
2
|
+
|
3
|
+
require "sorbet-runtime"
|
4
|
+
require "capnp"
|
5
|
+
require_relative "schema.capnp"
|
6
|
+
|
7
|
+
class Capnp::Generator
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { params(request_ref: Capnp::Reference).void }
|
11
|
+
def initialize(request_ref)
|
12
|
+
request = Schema::CodeGeneratorRequest.from_pointer(request_ref)
|
13
|
+
raise "Invalid CodeGeneratorRequest" if request.nil?
|
14
|
+
|
15
|
+
nodes = request.nodes
|
16
|
+
raise "No nodes found" if nodes.nil?
|
17
|
+
|
18
|
+
requested_files = request.requested_files
|
19
|
+
raise "No requested files found" if requested_files.nil?
|
20
|
+
|
21
|
+
requested_file_ids = requested_files.map(&:id)
|
22
|
+
|
23
|
+
# Build a hash of nodes by id and gather all requested file nodes
|
24
|
+
@nodes_by_id = T.let({}, T::Hash[Integer, Schema::Node])
|
25
|
+
@files = T.let([], T::Array[Schema::Node])
|
26
|
+
nodes.each do |node|
|
27
|
+
@nodes_by_id[node.id] = node
|
28
|
+
if node.is_file? && requested_file_ids.include?(node.id)
|
29
|
+
@files << node
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Gather all nodes that will become classes
|
34
|
+
@node_to_class_path = T.let({}, T::Hash[Integer, T::Array[String]])
|
35
|
+
@files.each do |file|
|
36
|
+
name = file_to_module_name(file)
|
37
|
+
@node_to_class_path.merge!(find_classes(file, [name]))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
sig { params(node: Schema::Node, path: T::Array[String]).returns(T::Hash[Integer, T::Array[String]]) }
|
42
|
+
def find_classes(node, path)
|
43
|
+
# Skip constants and annotations
|
44
|
+
return {} if node.is_const? || node.is_annotation?
|
45
|
+
|
46
|
+
result = T.let({node.id => path}, T::Hash[Integer, T::Array[String]])
|
47
|
+
|
48
|
+
nested_nodes = node.nested_nodes
|
49
|
+
return result if nested_nodes.nil?
|
50
|
+
|
51
|
+
# Recurse into nested nodes
|
52
|
+
nested_nodes.each do |nestednode|
|
53
|
+
name = nestednode.name&.to_s
|
54
|
+
raise "Node without a name" if name.nil?
|
55
|
+
|
56
|
+
new_path = path + [name]
|
57
|
+
result.merge!(find_classes(@nodes_by_id.fetch(nestednode.id), new_path))
|
58
|
+
end
|
59
|
+
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
# Convert from camelCase to CapitalCase
|
64
|
+
sig { params(name: String).returns(String) }
|
65
|
+
def class_name(name) = "#{name[0]&.upcase}#{name[1..]}"
|
66
|
+
|
67
|
+
# Convert from camelCase to snake_case
|
68
|
+
sig { params(name: String).returns(String) }
|
69
|
+
def method_name(name) = name.gsub(/([^A-Z])([A-Z]+)/, '\1_\2').downcase
|
70
|
+
|
71
|
+
# Convert from camelCase to SCREAMING_SNAKE_CASE
|
72
|
+
sig { params(name: String).returns(String) }
|
73
|
+
def const_name(name) = name.gsub(/([^A-Z])([A-Z]+)/, '\1_\2').upcase
|
74
|
+
|
75
|
+
sig { params(file: Schema::Node).returns(String) }
|
76
|
+
def file_to_module_name(file) = class_name(file.display_name&.to_s&.split("/")&.last&.sub(".capnp", "") || "")
|
77
|
+
|
78
|
+
sig { void }
|
79
|
+
def generate
|
80
|
+
@files.each do |file|
|
81
|
+
nested_nodes = file.nested_nodes
|
82
|
+
next "" if nested_nodes.nil?
|
83
|
+
|
84
|
+
nested_nodes_code = nested_nodes.flat_map do |nestednode|
|
85
|
+
name = nestednode.name&.to_s
|
86
|
+
raise "Node without a name" if name.nil?
|
87
|
+
node = @nodes_by_id[nestednode.id]
|
88
|
+
raise "Node not found" if node.nil?
|
89
|
+
process_node(name, node)
|
90
|
+
end
|
91
|
+
|
92
|
+
code = [
|
93
|
+
"# typed: strict",
|
94
|
+
"",
|
95
|
+
'require "capnp"',
|
96
|
+
'require "sorbet-runtime"',
|
97
|
+
"module #{file_to_module_name(file)}",
|
98
|
+
*nested_nodes_code.map { " #{_1}" }[1..],
|
99
|
+
"end",
|
100
|
+
""
|
101
|
+
].map(&:rstrip).join("\n")
|
102
|
+
|
103
|
+
# TODO: Use RedquestedFile.filename
|
104
|
+
path = "#{file.display_name&.to_s}.rb"
|
105
|
+
File.write(path, code)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
sig { params(name: String, node: Schema::Node).returns(T::Array[String]) }
|
110
|
+
def process_node(name, node)
|
111
|
+
which_val = node.which?
|
112
|
+
case which_val
|
113
|
+
when Schema::Node::Which::Struct
|
114
|
+
process_struct(name, node)
|
115
|
+
when Schema::Node::Which::Enum
|
116
|
+
process_enum(name, node)
|
117
|
+
when Schema::Node::Which::Interface
|
118
|
+
warn "Ignoring interface node"
|
119
|
+
[]
|
120
|
+
when Schema::Node::Which::Const
|
121
|
+
value = node.const.value
|
122
|
+
raise "Const without a value" if value.nil?
|
123
|
+
["#{const_name(name)} = #{process_value(value)}"]
|
124
|
+
when Schema::Node::Which::Annotation
|
125
|
+
warn "Ignoring annotation node"
|
126
|
+
[]
|
127
|
+
when Schema::Node::Which::File
|
128
|
+
raise "Unexpected file node"
|
129
|
+
else
|
130
|
+
T.absurd(which_val)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
sig { params(name: String, node: Schema::Node).returns(T::Array[String]) }
|
135
|
+
def process_struct(name, node)
|
136
|
+
raise "Generic structs are not supported" if node.is_generic
|
137
|
+
|
138
|
+
fields = node.struct.fields
|
139
|
+
raise "No fields found" if fields.nil?
|
140
|
+
|
141
|
+
field_code = fields.sort_by(&:code_order).flat_map do |field|
|
142
|
+
process_field(field)
|
143
|
+
end
|
144
|
+
|
145
|
+
nested_node_code = node.nested_nodes&.flat_map do |nestednode|
|
146
|
+
nested_node_name = nestednode.name&.to_s
|
147
|
+
raise "Node without a name" if nested_node_name.nil?
|
148
|
+
nested_node = @nodes_by_id.fetch(nestednode.id)
|
149
|
+
|
150
|
+
process_node(nested_node_name, nested_node)
|
151
|
+
end
|
152
|
+
nested_node_code ||= []
|
153
|
+
|
154
|
+
name = class_name(name)
|
155
|
+
|
156
|
+
list_class_code = if node.struct.is_group
|
157
|
+
[]
|
158
|
+
else
|
159
|
+
[
|
160
|
+
"",
|
161
|
+
" class List < Capnp::StructList",
|
162
|
+
" Elem = type_member { {fixed: #{name}} }",
|
163
|
+
" sig { override.returns(T.class_of(#{name})) }",
|
164
|
+
" def element_class = #{name}",
|
165
|
+
" end"
|
166
|
+
]
|
167
|
+
end
|
168
|
+
|
169
|
+
# Create Which enum class for unions
|
170
|
+
which_code = if node.struct.discriminant_count.zero?
|
171
|
+
[]
|
172
|
+
else
|
173
|
+
discriminant_offset = node.struct.discriminant_offset * 2
|
174
|
+
enumerants = fields
|
175
|
+
.reject { _1.discriminant_value == Schema::Field::NO_DISCRIMINANT }
|
176
|
+
.sort_by(&:discriminant_value)
|
177
|
+
.map { _1.name&.to_s || "" }
|
178
|
+
[
|
179
|
+
"sig { returns(Which) }",
|
180
|
+
"def which? = Which.from_integer(read_u16(#{discriminant_offset}, 0))",
|
181
|
+
*process_enumeration("Which", enumerants)
|
182
|
+
]
|
183
|
+
end
|
184
|
+
|
185
|
+
# Create to_obj method
|
186
|
+
to_obj_code = create_struct_to_obj(fields)
|
187
|
+
|
188
|
+
[
|
189
|
+
"",
|
190
|
+
"class #{name} < Capnp::Struct",
|
191
|
+
*field_code.map { " #{_1}" },
|
192
|
+
*nested_node_code.map { " #{_1}" },
|
193
|
+
*list_class_code,
|
194
|
+
*which_code.map { " #{_1}" },
|
195
|
+
*to_obj_code.map { " #{_1}" },
|
196
|
+
"end"
|
197
|
+
]
|
198
|
+
end
|
199
|
+
|
200
|
+
sig { params(fields: T::Enumerable[Schema::Field]).returns(T::Array[[String, String]]) }
|
201
|
+
def create_struct_to_obj_assignments(fields)
|
202
|
+
fields.map do |field|
|
203
|
+
name = field.name&.to_s
|
204
|
+
raise "Field without a name" if name.nil?
|
205
|
+
|
206
|
+
mname = method_name(name)
|
207
|
+
|
208
|
+
assignment = if field.is_group?
|
209
|
+
# Group "fields" are treated as nested structs
|
210
|
+
"res[#{mname.inspect}] = #{mname}.to_obj"
|
211
|
+
else
|
212
|
+
# Normal (non-group) fields
|
213
|
+
type = field.slot.type
|
214
|
+
raise "Field without a type" if type.nil?
|
215
|
+
|
216
|
+
case type.which?
|
217
|
+
when Schema::Type::Which::Text, Schema::Type::Which::Data, Schema::Type::Which::List, Schema::Type::Which::Struct
|
218
|
+
"res[#{mname.inspect}] = #{mname}&.to_obj"
|
219
|
+
when Schema::Type::Which::Interface, Schema::Type::Which::AnyPointer
|
220
|
+
warn "Interfaces and AnyPointers cannot be converted to objects"
|
221
|
+
"res[#{mname.inspect}] = #{mname}"
|
222
|
+
else
|
223
|
+
"res[#{mname.inspect}] = #{mname}"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
[name, assignment]
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
sig { params(fields: T::Enumerable[Schema::Field]).returns(T::Array[String]) }
|
232
|
+
def create_struct_to_obj(fields)
|
233
|
+
# Split up union and non-union fields
|
234
|
+
normal, union = fields
|
235
|
+
.sort_by(&:code_order)
|
236
|
+
.partition { _1.discriminant_value == Schema::Field::NO_DISCRIMINANT }
|
237
|
+
|
238
|
+
# Process normal fields
|
239
|
+
assignments = create_struct_to_obj_assignments(normal).map { " #{_2}" }
|
240
|
+
|
241
|
+
# Process union fields with a case statement
|
242
|
+
union_assignments = if union.empty?
|
243
|
+
[]
|
244
|
+
else
|
245
|
+
whens = create_struct_to_obj_assignments(union).map do |name, assignment|
|
246
|
+
" when Which::#{class_name(name)} then #{assignment}"
|
247
|
+
end
|
248
|
+
[
|
249
|
+
" case which?",
|
250
|
+
*whens,
|
251
|
+
" end"
|
252
|
+
]
|
253
|
+
end
|
254
|
+
|
255
|
+
[
|
256
|
+
"sig { override.returns(Object) }",
|
257
|
+
"def to_obj",
|
258
|
+
" res = {}",
|
259
|
+
*assignments,
|
260
|
+
*union_assignments,
|
261
|
+
" res",
|
262
|
+
"end"
|
263
|
+
]
|
264
|
+
end
|
265
|
+
|
266
|
+
sig { params(field: Schema::Field).returns(T::Array[String]) }
|
267
|
+
def process_field(field)
|
268
|
+
# TODO: Check union discriminant values
|
269
|
+
warn "Ignoring annotations" unless field.annotations&.length.to_i.zero?
|
270
|
+
|
271
|
+
name = field.name&.to_s
|
272
|
+
raise "Field without a name" if name.nil?
|
273
|
+
|
274
|
+
mname = method_name(name)
|
275
|
+
|
276
|
+
getter_def = if field.is_group?
|
277
|
+
group_node = @nodes_by_id.fetch(field.group.type_id)
|
278
|
+
class_name = "Group#{class_name(name)}"
|
279
|
+
group_class_code = process_struct(class_name, group_node)
|
280
|
+
[
|
281
|
+
"sig { returns(#{class_name}) }",
|
282
|
+
"def #{mname} = #{class_name}.new(@data, @data_size, @pointers, @pointers_size)",
|
283
|
+
*group_class_code
|
284
|
+
]
|
285
|
+
else
|
286
|
+
type = field.slot.type
|
287
|
+
raise "Field without a type" if type.nil?
|
288
|
+
|
289
|
+
default_variable = "DEFAULT_#{const_name(name)}"
|
290
|
+
|
291
|
+
which_type = type.which?
|
292
|
+
case which_type
|
293
|
+
when Schema::Type::Which::Void
|
294
|
+
[
|
295
|
+
"sig { returns(NilClass) }",
|
296
|
+
"def #{mname} = nil"
|
297
|
+
]
|
298
|
+
when Schema::Type::Which::Bool
|
299
|
+
default_value = field.slot.default_value&.bool ? "0xFF" : "0x00"
|
300
|
+
offset = field.slot.offset / 8
|
301
|
+
mask = (1 << (field.slot.offset % 8)).to_s(16)
|
302
|
+
[
|
303
|
+
"#{default_variable} = #{field.slot.default_value&.bool == true}",
|
304
|
+
"sig { returns(T::Boolean) }",
|
305
|
+
"def #{mname} = (read_u8(#{offset}, #{default_value}) & 0x#{mask}) != 0"
|
306
|
+
]
|
307
|
+
when Schema::Type::Which::Int8
|
308
|
+
default_value = field.slot.default_value&.int8 || 0
|
309
|
+
[
|
310
|
+
"#{default_variable} = #{default_value}",
|
311
|
+
"sig { returns(Integer) }",
|
312
|
+
"def #{mname} = read_s8(#{field.slot.offset}, #{default_value})"
|
313
|
+
]
|
314
|
+
when Schema::Type::Which::Int16
|
315
|
+
default_value = field.slot.default_value&.int16 || 0
|
316
|
+
offset = field.slot.offset * 2
|
317
|
+
[
|
318
|
+
"#{default_variable} = #{default_value}",
|
319
|
+
"sig { returns(Integer) }",
|
320
|
+
"def #{mname} = read_s16(#{offset}, #{default_value})"
|
321
|
+
]
|
322
|
+
when Schema::Type::Which::Int32
|
323
|
+
default_value = field.slot.default_value&.int32 || 0
|
324
|
+
offset = field.slot.offset * 4
|
325
|
+
[
|
326
|
+
"#{default_variable} = #{default_value}",
|
327
|
+
"sig { returns(Integer) }",
|
328
|
+
"def #{mname} = read_s32(#{offset}, #{default_value})"
|
329
|
+
]
|
330
|
+
when Schema::Type::Which::Int64
|
331
|
+
default_value = field.slot.default_value&.int64 || 0
|
332
|
+
offset = field.slot.offset * 8
|
333
|
+
[
|
334
|
+
"#{default_variable} = #{default_value}",
|
335
|
+
"sig { returns(Integer) }",
|
336
|
+
"def #{mname} = read_s64(#{offset}, #{default_value})"
|
337
|
+
]
|
338
|
+
when Schema::Type::Which::Uint8
|
339
|
+
default_value = field.slot.default_value&.uint8 || 0
|
340
|
+
[
|
341
|
+
"#{default_variable} = #{default_value}",
|
342
|
+
"sig { returns(Integer) }",
|
343
|
+
"def #{mname} = read_u8(#{field.slot.offset}, #{default_value})"
|
344
|
+
]
|
345
|
+
when Schema::Type::Which::Uint16
|
346
|
+
default_value = field.slot.default_value&.uint16 || 0
|
347
|
+
offset = field.slot.offset * 2
|
348
|
+
[
|
349
|
+
"#{default_variable} = #{default_value}",
|
350
|
+
"sig { returns(Integer) }",
|
351
|
+
"def #{mname} = read_u16(#{offset}, #{default_value})"
|
352
|
+
]
|
353
|
+
when Schema::Type::Which::Uint32
|
354
|
+
default_value = field.slot.default_value&.uint32 || 0
|
355
|
+
offset = field.slot.offset * 4
|
356
|
+
[
|
357
|
+
"#{default_variable} = #{default_value}",
|
358
|
+
"sig { returns(Integer) }",
|
359
|
+
"def #{mname} = read_u32(#{offset}, #{default_value})"
|
360
|
+
]
|
361
|
+
when Schema::Type::Which::Uint64
|
362
|
+
default_value = field.slot.default_value&.uint64 || 0
|
363
|
+
offset = field.slot.offset * 8
|
364
|
+
[
|
365
|
+
"#{default_variable} = #{default_value}",
|
366
|
+
"sig { returns(Integer) }",
|
367
|
+
"def #{mname} = read_u64(#{offset}, #{default_value})"
|
368
|
+
]
|
369
|
+
when Schema::Type::Which::Float32
|
370
|
+
default_value = field.slot.default_value&.float32 || 0.0
|
371
|
+
offset = field.slot.offset * 4
|
372
|
+
[
|
373
|
+
"#{default_variable} = #{default_value}",
|
374
|
+
"sig { returns(Float) }",
|
375
|
+
"def #{mname} = read_f32(#{offset}, #{default_value})"
|
376
|
+
]
|
377
|
+
when Schema::Type::Which::Float64
|
378
|
+
default_value = field.slot.default_value&.float64 || 0.0
|
379
|
+
offset = field.slot.offset * 8
|
380
|
+
[
|
381
|
+
"#{default_variable} = #{default_value}",
|
382
|
+
"sig { returns(Float) }",
|
383
|
+
"def #{mname} = read_f64(#{offset}, #{default_value})"
|
384
|
+
]
|
385
|
+
when Schema::Type::Which::Text
|
386
|
+
default_value = field.slot.default_value&.text&.to_s.inspect
|
387
|
+
apply_default = field.slot.had_explicit_default ? " || Capnp::ObjectString.new(#{default_variable})" : ""
|
388
|
+
[
|
389
|
+
"#{default_variable} = #{default_value}",
|
390
|
+
"sig { returns(T.nilable(Capnp::String)) }",
|
391
|
+
"def #{mname} = Capnp::BufferString.from_pointer(read_pointer(#{field.slot.offset}))#{apply_default}"
|
392
|
+
]
|
393
|
+
when Schema::Type::Which::Data
|
394
|
+
default_value = field.slot.default_value&.data&.value.inspect
|
395
|
+
apply_default = field.slot.had_explicit_default ? " || #{default_variable}" : ""
|
396
|
+
[
|
397
|
+
"#{default_variable} = #{default_value}",
|
398
|
+
"sig { returns(T.nilable(Capnp::Data)) }",
|
399
|
+
"def #{mname} = Capnp::Data.from_pointer(read_pointer(#{field.slot.offset}))#{apply_default}"
|
400
|
+
]
|
401
|
+
when Schema::Type::Which::List
|
402
|
+
raise "List default values not supported" if field.slot.had_explicit_default
|
403
|
+
element_class = type.list.element_type
|
404
|
+
raise "List without an element type" if element_class.nil?
|
405
|
+
which_element_type = element_class.which?
|
406
|
+
case which_element_type
|
407
|
+
when Schema::Type::Which::Void
|
408
|
+
raise "Void list elements not supported"
|
409
|
+
when Schema::Type::Which::Bool
|
410
|
+
raise "Bool list elements not supported"
|
411
|
+
when Schema::Type::Which::Int8, Schema::Type::Which::Int16, Schema::Type::Which::Int32, Schema::Type::Which::Int64
|
412
|
+
list_class = "Capnp::SignedIntegerList"
|
413
|
+
element_class = "Integer"
|
414
|
+
when Schema::Type::Which::Uint8, Schema::Type::Which::Uint16, Schema::Type::Which::Uint32, Schema::Type::Which::Uint64
|
415
|
+
list_class = "Capnp::UnsignedIntegerList"
|
416
|
+
element_class = "Integer"
|
417
|
+
when Schema::Type::Which::Float32, Schema::Type::Which::Float64
|
418
|
+
list_class = "Capnp::FloatList"
|
419
|
+
element_class = "Float"
|
420
|
+
when Schema::Type::Which::Text
|
421
|
+
raise "Text list elements not supported"
|
422
|
+
when Schema::Type::Which::Data
|
423
|
+
raise "Data list elements not supported"
|
424
|
+
when Schema::Type::Which::List
|
425
|
+
raise "List list elements not supported"
|
426
|
+
when Schema::Type::Which::Enum
|
427
|
+
raise "Enum list elements not supported"
|
428
|
+
when Schema::Type::Which::Struct
|
429
|
+
raise "List[Struct] default values not supported" if field.slot.had_explicit_default
|
430
|
+
element_class = @node_to_class_path.fetch(element_class.struct.type_id).join("::")
|
431
|
+
list_class = "#{element_class}::List"
|
432
|
+
when Schema::Type::Which::Interface
|
433
|
+
raise "Interface list elements not supported"
|
434
|
+
when Schema::Type::Which::AnyPointer
|
435
|
+
raise "AnyPointer list elements not supported"
|
436
|
+
else
|
437
|
+
T.absurd(which_element_type)
|
438
|
+
end
|
439
|
+
|
440
|
+
[
|
441
|
+
"sig { returns(T.nilable(Capnp::List[#{element_class}])) }",
|
442
|
+
"def #{mname} = #{list_class}.from_pointer(read_pointer(#{field.slot.offset}))"
|
443
|
+
]
|
444
|
+
when Schema::Type::Which::Enum
|
445
|
+
enumerants = @nodes_by_id.fetch(type.enum.type_id).enum.enumerants
|
446
|
+
raise "No enumerants found" if enumerants.nil?
|
447
|
+
|
448
|
+
default_num = field.slot.default_value&.enum || 0
|
449
|
+
default_value = class_name(enumerants[default_num]&.name&.to_s || "")
|
450
|
+
|
451
|
+
offset = field.slot.offset * 2
|
452
|
+
class_path = @node_to_class_path.fetch(type.enum.type_id).join("::")
|
453
|
+
[
|
454
|
+
# TODO: This doesn't work if the enum class is declared after this field
|
455
|
+
"# #{default_variable} = #{class_path}::#{default_value}",
|
456
|
+
"sig { returns(#{class_path}) }",
|
457
|
+
"def #{mname} = #{class_path}.from_integer(read_u16(#{offset}, #{default_num}))"
|
458
|
+
]
|
459
|
+
when Schema::Type::Which::Struct
|
460
|
+
raise "Struct default values not supported" if field.slot.had_explicit_default
|
461
|
+
class_path = @node_to_class_path.fetch(type.struct.type_id).join("::")
|
462
|
+
[
|
463
|
+
"sig { returns(T.nilable(#{class_path})) }",
|
464
|
+
"def #{mname} = #{class_path}.from_pointer(read_pointer(#{field.slot.offset}))"
|
465
|
+
]
|
466
|
+
when Schema::Type::Which::Interface
|
467
|
+
raise "Interface fields not supported"
|
468
|
+
when Schema::Type::Which::AnyPointer
|
469
|
+
raise "Only unconstrained AnyPointers are supported" unless type.any_pointer.is_unconstrained?
|
470
|
+
[
|
471
|
+
"sig { returns(Capnp::Reference) }",
|
472
|
+
"def #{mname} = read_pointer(#{field.slot.offset})"
|
473
|
+
]
|
474
|
+
else
|
475
|
+
T.absurd(which_type)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
# Add type checking methods for union fields
|
480
|
+
if field.discriminant_value != Schema::Field::NO_DISCRIMINANT
|
481
|
+
getter_def += [
|
482
|
+
"sig { returns(T::Boolean) }",
|
483
|
+
"def is_#{mname}? = which? == Which::#{class_name(name)}"
|
484
|
+
]
|
485
|
+
end
|
486
|
+
|
487
|
+
getter_def
|
488
|
+
end
|
489
|
+
|
490
|
+
sig { params(name: String, node: Schema::Node).returns(T::Array[String]) }
|
491
|
+
def process_enum(name, node)
|
492
|
+
raise "Nested nodes not supported in enum" unless node.nested_nodes&.length.to_i.zero?
|
493
|
+
raise "Generic structs are not supported" if node.is_generic
|
494
|
+
|
495
|
+
enumerants = node.enum.enumerants
|
496
|
+
raise "No enumerants found" if enumerants.nil?
|
497
|
+
|
498
|
+
# Enumerants are ordered by their numeric value
|
499
|
+
enums = enumerants.map do |enumerant|
|
500
|
+
warn "Ignoring annotations" unless enumerant.annotations&.length.to_i.zero?
|
501
|
+
|
502
|
+
enumerant_name = enumerant.name&.to_s
|
503
|
+
raise "Enumerant without a name" if enumerant_name.nil?
|
504
|
+
enumerant_name
|
505
|
+
end
|
506
|
+
|
507
|
+
process_enumeration(name, enums)
|
508
|
+
end
|
509
|
+
|
510
|
+
sig { params(name: String, enumerants: T::Array[String]).returns(T::Array[String]) }
|
511
|
+
def process_enumeration(name, enumerants)
|
512
|
+
definitions = T.let([], T::Array[String])
|
513
|
+
from_int = T.let([], T::Array[String])
|
514
|
+
|
515
|
+
# Enumerants are ordered by their numeric value
|
516
|
+
enumerants.each_with_index do |enumerant_name, ix|
|
517
|
+
ename = class_name(enumerant_name)
|
518
|
+
definitions << " #{ename} = new(#{enumerant_name.inspect})"
|
519
|
+
from_int << " when #{ix} then #{ename}"
|
520
|
+
end
|
521
|
+
|
522
|
+
# TODO: Define an Capnp::Enum class
|
523
|
+
class_name = class_name(name)
|
524
|
+
[
|
525
|
+
"",
|
526
|
+
"class #{class_name} < T::Enum",
|
527
|
+
" extend T::Sig",
|
528
|
+
" enums do",
|
529
|
+
*definitions,
|
530
|
+
" end",
|
531
|
+
" sig { params(value: Integer).returns(#{class_name}) }",
|
532
|
+
" def self.from_integer(value)",
|
533
|
+
" case value",
|
534
|
+
*from_int,
|
535
|
+
" else raise \"Unknown #{name} value: \#{value}\"",
|
536
|
+
" end",
|
537
|
+
" end",
|
538
|
+
"end"
|
539
|
+
]
|
540
|
+
end
|
541
|
+
|
542
|
+
sig { params(value: Schema::Value).returns(T.any(String, T::Boolean, Numeric, NilClass)) }
|
543
|
+
def process_value(value)
|
544
|
+
which_value = value.which?
|
545
|
+
case which_value
|
546
|
+
when Schema::Value::Which::Void then nil
|
547
|
+
when Schema::Value::Which::Bool then value.bool
|
548
|
+
when Schema::Value::Which::Int8 then value.int8
|
549
|
+
when Schema::Value::Which::Int16 then value.int16
|
550
|
+
when Schema::Value::Which::Int32 then value.int32
|
551
|
+
when Schema::Value::Which::Int64 then value.int64
|
552
|
+
when Schema::Value::Which::Uint8 then value.uint8
|
553
|
+
when Schema::Value::Which::Uint16 then value.uint16
|
554
|
+
when Schema::Value::Which::Uint32 then value.uint32
|
555
|
+
when Schema::Value::Which::Uint64 then value.uint64
|
556
|
+
when Schema::Value::Which::Float32 then value.float32
|
557
|
+
when Schema::Value::Which::Float64 then value.float64
|
558
|
+
when Schema::Value::Which::Text then value.text&.to_s.inspect
|
559
|
+
when Schema::Value::Which::Data then value.data&.value.inspect
|
560
|
+
when Schema::Value::Which::List then raise "List values not supported"
|
561
|
+
when Schema::Value::Which::Enum then value.enum # TODO: Convert to enum class
|
562
|
+
when Schema::Value::Which::Struct then raise "Struct values not supported"
|
563
|
+
when Schema::Value::Which::Interface then nil
|
564
|
+
when Schema::Value::Which::AnyPointer then raise "AnyPointer values not supported"
|
565
|
+
else T.absurd(which_value)
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|