ynl 0.2.4 → 0.4.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 +4 -4
- data/CHANGELOG.md +20 -0
- data/Rakefile +7 -0
- data/lib/ynl/family.rb +15 -2
- data/lib/ynl/generator.rb +978 -88
- data/lib/ynl/models.rb +49 -12
- data/lib/ynl/parser.rb +140 -49
- data/lib/ynl/version.rb +1 -1
- data/lib/ynl.rb +46 -5
- data/sig/generated/ynl/family.rbs +24 -0
- data/sig/generated/ynl/generator.rbs +163 -0
- data/sig/generated/ynl/models.rbs +165 -0
- data/sig/generated/ynl/parser.rbs +62 -0
- data/sig/generated/ynl/version.rbs +5 -0
- data/sig/generated/ynl.rbs +119 -0
- metadata +14 -7
data/lib/ynl/generator.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
module Ynl
|
|
2
|
+
# Generates Ruby code from a parsed YNL specification.
|
|
2
3
|
class Generator
|
|
4
|
+
# @private
|
|
3
5
|
module Refinements
|
|
4
6
|
WORD_DELIM = /[ _-]+/
|
|
5
7
|
|
|
@@ -32,19 +34,30 @@ module Ynl
|
|
|
32
34
|
|
|
33
35
|
PRELUDE = -<<~RUBY
|
|
34
36
|
# frozen_string_literal: true
|
|
35
|
-
# rbs_inline: enabled
|
|
36
37
|
# This code is generated by Ynl::Generator. DO NOT EDIT.
|
|
37
38
|
require 'nl'
|
|
38
39
|
RUBY
|
|
39
40
|
|
|
41
|
+
OPERATION_FLAG_DOCS = {
|
|
42
|
+
'admin-perm' => 'Requires CAP_NET_ADMIN in the initial user namespace.',
|
|
43
|
+
'uns-admin-perm' => 'Requires CAP_NET_ADMIN in the user namespace owning the network namespace.',
|
|
44
|
+
}.freeze
|
|
45
|
+
KERNEL_DOC_REFERENCE = %r{Documentation/(?<path>[A-Za-z0-9_./+-]+)\.rst\b}
|
|
46
|
+
private_constant :OPERATION_FLAG_DOCS, :KERNEL_DOC_REFERENCE
|
|
47
|
+
|
|
48
|
+
# @rbs (Models::Family ynl, _Writable out) -> void
|
|
40
49
|
def initialize(ynl, out)
|
|
41
50
|
@ynl = ynl
|
|
42
51
|
@out = out
|
|
43
52
|
@indent = 0
|
|
44
53
|
end
|
|
45
54
|
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
# @rbs (?superclass: String?, ?namespace: String?, ?default_resolver: String?) -> void
|
|
56
|
+
def generate(superclass: nil, namespace: nil, default_resolver: nil)
|
|
57
|
+
raw = @ynl.protocol == 'netlink-raw'
|
|
58
|
+
@wire = raw ? '::Nl::Raw' : '::Nl::Genl'
|
|
59
|
+
superclass ||= raw ? '::Nl::Raw::Family' : '::Nl::Genl::Family'
|
|
60
|
+
build_selector_plan
|
|
48
61
|
|
|
49
62
|
emit_comment(@ynl.doc)
|
|
50
63
|
|
|
@@ -52,35 +65,62 @@ module Ynl
|
|
|
52
65
|
emit_class([*namespace, classname].join('::'), superclass) do
|
|
53
66
|
emit_const('NAME', @ynl.name.as_string_literal)
|
|
54
67
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
68
|
+
emit_const('PROTONUM', @ynl.protonum) if raw
|
|
69
|
+
emit_const('VERSION', @ynl.version) unless raw
|
|
70
|
+
emit_open(raw:, default_resolver:, classname:)
|
|
71
|
+
|
|
72
|
+
emit_mcast_groups
|
|
73
|
+
|
|
74
|
+
unless @ynl.consts.empty?
|
|
75
|
+
emit_module('Constants') do
|
|
76
|
+
@ynl.consts.each_value do |const|
|
|
77
|
+
emit_comment(const.doc)
|
|
78
|
+
emit_const(const.name.as_const_name, const_value_literal(const.value))
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
emit_value_definitions('Enums', @ynl.enums.values) { |_definition, entry| entry.value }
|
|
84
|
+
emit_value_definitions('Flags', @ynl.flags.values + enums_used_as_flags) do |definition, entry|
|
|
85
|
+
definition.is_a?(Models::Enum) ? 1 << entry.value : entry.value
|
|
59
86
|
end
|
|
60
87
|
|
|
88
|
+
deferred_struct_consts = []
|
|
61
89
|
emit_module('Structs') do
|
|
62
90
|
@ynl.structs.each do |name, struct|
|
|
63
91
|
emit_comment(struct.doc)
|
|
64
92
|
write(name.as_class_name, " = Struct.new(")
|
|
65
93
|
indent do
|
|
66
94
|
struct.members.each do |member|
|
|
67
|
-
write(member.name.as_variable_name.as_symbol_literal, ', #: ', member.type
|
|
95
|
+
write(member.name.as_variable_name.as_symbol_literal, ', #: ', struct_member_rbs_type(member.type))
|
|
68
96
|
end
|
|
69
97
|
end
|
|
70
98
|
write(')')
|
|
71
99
|
emit_class(name.as_class_name) do
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
100
|
+
struct.members.each do |member|
|
|
101
|
+
emit_yard_attribute(
|
|
102
|
+
member.name.as_method_name,
|
|
103
|
+
struct_member_yard_type(member.type),
|
|
104
|
+
member.doc,
|
|
105
|
+
)
|
|
106
|
+
end
|
|
107
|
+
members = struct.members.map do |member|
|
|
108
|
+
"#{member.name.as_variable_name}: #{to_struct_member_datatype(member.type)}"
|
|
109
|
+
end
|
|
110
|
+
members = "Ractor.make_shareable({#{members.join(', ')}})"
|
|
111
|
+
if struct.members.any? { it.type.is_a?(Types::Binary) && it.type.struct }
|
|
112
|
+
deferred_struct_consts << ["Structs::#{name.as_class_name}::MEMBERS", members]
|
|
113
|
+
else
|
|
114
|
+
emit_const('MEMBERS', members, rbs: 'Hash[::Symbol, ::Nl::_DataType]', internal: true)
|
|
115
|
+
end
|
|
78
116
|
|
|
79
117
|
emit_comment('Decodes the struct.')
|
|
80
118
|
emit_rbs_comment(
|
|
81
119
|
'decoder: ::Nl::Decoder',
|
|
82
120
|
'return: instance',
|
|
83
121
|
)
|
|
122
|
+
emit_yard_param('decoder', 'Nl::Decoder')
|
|
123
|
+
emit_yard_return('self')
|
|
84
124
|
write('def self.decode(decoder)')
|
|
85
125
|
indent do
|
|
86
126
|
write('self.new(*MEMBERS.map {|name, datatype| datatype.decode(decoder) })')
|
|
@@ -92,6 +132,8 @@ module Ynl
|
|
|
92
132
|
'encoder: ::Nl::Encoder',
|
|
93
133
|
'return: void',
|
|
94
134
|
)
|
|
135
|
+
emit_yard_param('encoder', 'Nl::Encoder')
|
|
136
|
+
emit_yard_return('void')
|
|
95
137
|
write('def encode(encoder)')
|
|
96
138
|
indent do
|
|
97
139
|
write('MEMBERS.each {|name, datatype| datatype.encode(encoder, self.public_send(name)) }')
|
|
@@ -100,49 +142,91 @@ module Ynl
|
|
|
100
142
|
end
|
|
101
143
|
end
|
|
102
144
|
end
|
|
145
|
+
deferred_struct_consts.each do |name, members|
|
|
146
|
+
emit_const(name, members, rbs: 'Hash[::Symbol, ::Nl::_DataType]', internal: true)
|
|
147
|
+
end
|
|
103
148
|
|
|
149
|
+
deferred_sub_message_datatypes = []
|
|
104
150
|
emit_module('AttributeSets') do
|
|
105
151
|
deferred_consts = []
|
|
106
152
|
@ynl.attribute_sets.each do |name, attr_set|
|
|
107
153
|
emit_comment(attr_set.doc)
|
|
108
|
-
emit_class(name.as_class_name,
|
|
154
|
+
emit_class(name.as_class_name, '::Nl::AttributeSet') do
|
|
109
155
|
emit_comment("Abstract class")
|
|
110
|
-
emit_class('Attribute',
|
|
156
|
+
emit_class('Attribute', '::Nl::AttributeSet::Attribute') do
|
|
111
157
|
end
|
|
112
158
|
attr_set.attributes.each do |attr|
|
|
113
|
-
emit_comment(attr
|
|
159
|
+
emit_comment(attribute_doc(attr))
|
|
114
160
|
emit_class(attr.name.as_class_name, 'Attribute') do
|
|
115
161
|
emit_const('TYPE', attr.value)
|
|
116
162
|
emit_const('NAME', attr.name.as_variable_name.as_symbol_literal)
|
|
117
|
-
if attr.
|
|
163
|
+
emit_const('MULTI', 'true', internal: true) if attr.multi?
|
|
164
|
+
emit_const('ORDER', @attribute_orders.fetch(attr_set).fetch(attr), internal: true)
|
|
165
|
+
if slot = @local_selectors.fetch(attr_set).index(attr.name)
|
|
166
|
+
emit_const('SELECTOR_SLOT', slot, internal: true)
|
|
167
|
+
end
|
|
168
|
+
if attr.type.is_a?(Types::SubMessage)
|
|
169
|
+
deferred_sub_message_datatypes << [
|
|
170
|
+
"AttributeSets::#{name.as_class_name}::#{attr.name.as_class_name}::DATATYPE",
|
|
171
|
+
to_datatype(attr.type, attr.checks, owner: attr_set),
|
|
172
|
+
]
|
|
173
|
+
elsif attr.type.is_a?(Types::NestedAttributes) ||
|
|
174
|
+
attr.type.is_a?(Types::NestTypeValue) ||
|
|
118
175
|
(attr.type.is_a?(Types::IndexedArray) && attr.type.sub_type.is_a?(Types::NestedAttributes))
|
|
119
|
-
deferred_consts << ["#{name.as_class_name}::#{attr.name.as_class_name}::DATATYPE", to_datatype(attr.type, attr.checks)]
|
|
176
|
+
deferred_consts << ["#{name.as_class_name}::#{attr.name.as_class_name}::DATATYPE", to_datatype(attr.type, attr.checks, owner: attr_set)]
|
|
120
177
|
else
|
|
121
|
-
emit_const('DATATYPE', to_datatype(attr.type, attr.checks))
|
|
178
|
+
emit_const('DATATYPE', to_datatype(attr.type, attr.checks, owner: attr_set), internal: true)
|
|
122
179
|
end
|
|
123
180
|
end
|
|
124
181
|
end
|
|
125
182
|
|
|
126
|
-
|
|
183
|
+
selector_names = @local_selectors.fetch(attr_set)
|
|
184
|
+
external_selector_names = @external_selectors.fetch(attr_set)
|
|
185
|
+
unless selector_names.empty? && external_selector_names.empty?
|
|
186
|
+
emit_const(
|
|
187
|
+
'SELECTOR_NAMES',
|
|
188
|
+
"Ractor.make_shareable({local: [#{selector_names.map { it.as_variable_name.as_symbol_literal }.join(', ')}], external: [#{external_selector_names.map { it.as_variable_name.as_symbol_literal }.join(', ')}]})",
|
|
189
|
+
internal: true,
|
|
190
|
+
)
|
|
191
|
+
end
|
|
192
|
+
|
|
127
193
|
emit_const(
|
|
128
194
|
'BY_NAME',
|
|
129
195
|
"Ractor.make_shareable({#{attr_set.attributes.map { "#{it.name.as_variable_name.as_symbol_literal} => #{it.name.as_class_name}" }.join(', ') }})",
|
|
130
196
|
rbs: 'Hash[::Symbol, Attribute]',
|
|
197
|
+
internal: true,
|
|
131
198
|
)
|
|
132
199
|
|
|
133
|
-
emit_nodoc
|
|
134
200
|
emit_const(
|
|
135
201
|
'BY_TYPE',
|
|
136
202
|
"Ractor.make_shareable({#{attr_set.attributes.map { "#{it.value} => #{it.name.as_class_name}" }.join(', ') }})",
|
|
137
203
|
rbs: 'Hash[::Integer, Attribute]',
|
|
204
|
+
internal: true,
|
|
138
205
|
)
|
|
139
206
|
|
|
207
|
+
attr_set.attributes.each do |attribute|
|
|
208
|
+
next if attribute.type.is_a?(Types::Pad)
|
|
209
|
+
|
|
210
|
+
emit_comment(attribute_doc(attribute))
|
|
211
|
+
emit_rbs_comment('return: ' + attribute_rbs_type(attribute))
|
|
212
|
+
emit_yard_return(attribute_yard_type(attribute))
|
|
213
|
+
emit_yard_see(attribute.name.as_class_name)
|
|
214
|
+
expression = if attribute.multi?
|
|
215
|
+
"self[#{attribute.name.as_variable_name.as_symbol_literal}].map(&:value)"
|
|
216
|
+
else
|
|
217
|
+
"self[#{attribute.name.as_variable_name.as_symbol_literal}]&.value"
|
|
218
|
+
end
|
|
219
|
+
emit_getter(attribute.name.as_method_name, expression)
|
|
220
|
+
end
|
|
221
|
+
|
|
140
222
|
emit_singleton_class do
|
|
141
223
|
emit_comment('Looks up Attribute class by name.')
|
|
142
224
|
emit_rbs_comment(
|
|
143
225
|
'name: Symbol',
|
|
144
226
|
'return: Attribute',
|
|
145
227
|
)
|
|
228
|
+
emit_yard_param('name', 'Symbol')
|
|
229
|
+
emit_yard_return('Attribute')
|
|
146
230
|
emit_getter('by_name(name)', 'BY_NAME.fetch(name)')
|
|
147
231
|
|
|
148
232
|
emit_comment('Looks up Attribute class by type value.')
|
|
@@ -150,11 +234,21 @@ module Ynl
|
|
|
150
234
|
'type: Integer',
|
|
151
235
|
'return: Attribute',
|
|
152
236
|
)
|
|
237
|
+
emit_yard_param('type', 'Integer')
|
|
238
|
+
emit_yard_return('Attribute')
|
|
153
239
|
emit_getter('by_type(type)', 'BY_TYPE.fetch(type)')
|
|
154
240
|
end
|
|
155
241
|
end
|
|
156
242
|
end
|
|
157
|
-
deferred_consts.each
|
|
243
|
+
deferred_consts.each do |const|
|
|
244
|
+
emit_const(*const, internal: true)
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
emit_sub_messages
|
|
250
|
+
deferred_sub_message_datatypes.each do |const|
|
|
251
|
+
emit_const(*const, internal: true)
|
|
158
252
|
end
|
|
159
253
|
|
|
160
254
|
emit_module('Messages') do
|
|
@@ -162,58 +256,44 @@ module Ynl
|
|
|
162
256
|
%w[do dump].each do |method|
|
|
163
257
|
if request_reply = oper.public_send(method + 'it')
|
|
164
258
|
%w[request reply].to_h { [it, request_reply.public_send(it)] }.compact.each do |type, msg|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
emit_const('ATTRIBUTES', "Ractor.make_shareable(%i[#{attribute_params.map { it.as_variable_name }.join(' ')}])")
|
|
173
|
-
oper.fixed_header.members.each do |member|
|
|
174
|
-
param = member.name
|
|
175
|
-
datatype = member.type
|
|
176
|
-
next if datatype.is_a? Types::Pad
|
|
177
|
-
next if attribute_params.include?(param)
|
|
178
|
-
emit_comment("Gets the value of `#{param}` field in the message's fixed header.")
|
|
179
|
-
emit_rbs_comment(
|
|
180
|
-
'return: ' + datatype.rbs_type,
|
|
181
|
-
)
|
|
182
|
-
emit_getter(param.as_method_name, "fixed_header.#{param.as_method_name}")
|
|
183
|
-
end if oper.fixed_header
|
|
184
|
-
attribute_params.each do |param|
|
|
185
|
-
datatype = oper.attribute_set.attributes.find { it.name == param }.type
|
|
186
|
-
next if datatype.is_a? Types::Pad
|
|
187
|
-
extending = oper.fixed_header&.members&.any? { it.name == param }
|
|
188
|
-
|
|
189
|
-
if extending
|
|
190
|
-
emit_comment("Gets the value of `#{param}` attribute or fixed header in the message.")
|
|
191
|
-
else
|
|
192
|
-
emit_comment("Gets the value of `#{param}` attribute in the message.")
|
|
193
|
-
end
|
|
194
|
-
emit_rbs_comment(
|
|
195
|
-
'return: ' + datatype.rbs_type,
|
|
196
|
-
)
|
|
197
|
-
if extending
|
|
198
|
-
# If the fixed header and the attribute set have the same-name parameter, the value from the attribute should have
|
|
199
|
-
# the precedence over that from the header.
|
|
200
|
-
emit_getter(param.as_method_name, "attributes[#{param.as_variable_name.as_symbol_literal}]&.value || fixed_header.#{param.as_method_name}")
|
|
201
|
-
else
|
|
202
|
-
emit_getter(param.as_method_name, "attributes[#{param.as_variable_name.as_symbol_literal}]&.value")
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
end
|
|
259
|
+
emit_message_class(
|
|
260
|
+
method.as_class_name + oper.name.as_class_name + type.as_class_name,
|
|
261
|
+
msg,
|
|
262
|
+
fixed_header: oper.fixed_header,
|
|
263
|
+
attribute_set: oper.attribute_set,
|
|
264
|
+
doc: operation_doc(oper),
|
|
265
|
+
)
|
|
206
266
|
end
|
|
207
267
|
end
|
|
208
268
|
end
|
|
209
269
|
end
|
|
210
270
|
end
|
|
211
271
|
|
|
272
|
+
emit_module('Notifications') do
|
|
273
|
+
notification_operations.each do |oper|
|
|
274
|
+
message, fixed_header, attribute_set = notification_layout(oper)
|
|
275
|
+
emit_message_class(
|
|
276
|
+
oper.name.as_class_name,
|
|
277
|
+
message,
|
|
278
|
+
fixed_header:,
|
|
279
|
+
attribute_set:,
|
|
280
|
+
doc: notification_doc(oper),
|
|
281
|
+
)
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
emit_const(
|
|
286
|
+
'NOTIFICATIONS',
|
|
287
|
+
"Ractor.make_shareable({#{notification_operations.map { |oper| "#{oper.notification.message.value} => Notifications::#{oper.name.as_class_name}" }.join(', ') }})",
|
|
288
|
+
rbs: 'Hash[::Integer, ::Class]',
|
|
289
|
+
internal: true,
|
|
290
|
+
)
|
|
291
|
+
|
|
212
292
|
# emit request methods
|
|
213
293
|
@ynl.operations.each do |name, oper|
|
|
214
294
|
%w[do dump].each do |method|
|
|
215
295
|
if request_reply = oper.public_send(method + 'it')
|
|
216
|
-
|
|
296
|
+
request = request_reply.request
|
|
217
297
|
|
|
218
298
|
request_class = "Messages::#{method.as_class_name}#{oper.name.as_class_name}Request"
|
|
219
299
|
reply_class = request_reply.reply ? "Messages::#{method.as_class_name}#{oper.name.as_class_name}Reply" : 'nil'
|
|
@@ -224,15 +304,28 @@ module Ynl
|
|
|
224
304
|
params = header_params + attribute_params
|
|
225
305
|
params.reject! { it.type.is_a? Types::Pad }
|
|
226
306
|
|
|
227
|
-
rbs_params = params.map { |it| "?#{it.name.as_variable_name}: #{it
|
|
307
|
+
rbs_params = params.map { |it| "?#{it.name.as_variable_name}: #{parameter_rbs_type(it)}" }.join(', ')
|
|
228
308
|
rbs_result = request_reply.reply ? reply_class : 'void'
|
|
229
309
|
|
|
230
|
-
emit_comment(oper
|
|
310
|
+
emit_comment(operation_doc(oper))
|
|
231
311
|
if method == 'dump'
|
|
312
|
+
method_name = "#{method.as_method_name}_#{oper.name.as_method_name}"
|
|
232
313
|
emit_rbs_comment(
|
|
233
|
-
"(#{rbs_params}) ->
|
|
314
|
+
"(#{rbs_params}) -> Array[#{rbs_result}]\n | (#{rbs_params}) { (#{rbs_result}) -> void } -> void",
|
|
234
315
|
)
|
|
235
|
-
|
|
316
|
+
emit_yard_overload("#{method_name}(**args)") do |indentation|
|
|
317
|
+
emit_comment("#{indentation}Returns an array when no block is given.")
|
|
318
|
+
emit_yard_options(params, indentation:)
|
|
319
|
+
emit_yard_return("Array<#{rbs_result}>", indentation:)
|
|
320
|
+
end
|
|
321
|
+
emit_yard_overload("#{method_name}(**args, &block)") do |indentation|
|
|
322
|
+
emit_comment("#{indentation}Yields each reply when a block is given.")
|
|
323
|
+
emit_yard_options(params, indentation:)
|
|
324
|
+
emit_yard_yieldparam('reply', rbs_result, indentation:)
|
|
325
|
+
emit_yard_return('void', indentation:)
|
|
326
|
+
end
|
|
327
|
+
emit_yard_see(request_class)
|
|
328
|
+
write("def #{method_name}(**args, &block)")
|
|
236
329
|
indent do
|
|
237
330
|
write("exchange_message(#{method.as_symbol_literal}, #{request_class}, #{reply_class}, args, &block)")
|
|
238
331
|
end
|
|
@@ -240,6 +333,9 @@ module Ynl
|
|
|
240
333
|
emit_rbs_comment(
|
|
241
334
|
"(#{rbs_params}) -> #{rbs_result}",
|
|
242
335
|
)
|
|
336
|
+
emit_yard_options(params)
|
|
337
|
+
emit_yard_return(rbs_result)
|
|
338
|
+
emit_yard_see(request_class)
|
|
243
339
|
write("def #{method.as_method_name}_#{oper.name.as_method_name}(**args)")
|
|
244
340
|
indent do
|
|
245
341
|
write("exchange_message(#{method.as_symbol_literal}, #{request_class}, #{reply_class}, args)")
|
|
@@ -249,6 +345,56 @@ module Ynl
|
|
|
249
345
|
end
|
|
250
346
|
end
|
|
251
347
|
end
|
|
348
|
+
|
|
349
|
+
emit_class('AsyncOperations') do
|
|
350
|
+
emit_internal
|
|
351
|
+
write('def initialize(&exchange)')
|
|
352
|
+
indent do
|
|
353
|
+
write('@exchange = exchange')
|
|
354
|
+
end
|
|
355
|
+
write('end')
|
|
356
|
+
|
|
357
|
+
@ynl.operations.each do |name, oper|
|
|
358
|
+
%w[do dump].each do |method|
|
|
359
|
+
next unless request_reply = oper.public_send(method + 'it')
|
|
360
|
+
|
|
361
|
+
request_class = "Messages::#{method.as_class_name}#{oper.name.as_class_name}Request"
|
|
362
|
+
reply_class = request_reply.reply ? "Messages::#{method.as_class_name}#{oper.name.as_class_name}Reply" : 'nil'
|
|
363
|
+
header_params = oper.fixed_header&.members || []
|
|
364
|
+
attribute_params = oper.attribute_set.attributes.filter { request_reply.request.attributes.include?(it.name) }
|
|
365
|
+
attribute_params.reject! {|a| header_params.any? {|h| h.name == a.name } }
|
|
366
|
+
params = (header_params + attribute_params).reject { it.type.is_a? Types::Pad }
|
|
367
|
+
rbs_params = params.map { |it| "?#{it.name.as_variable_name}: #{parameter_rbs_type(it)}" }.join(', ')
|
|
368
|
+
result = request_reply.reply ? reply_class : 'void'
|
|
369
|
+
operation = method == 'dump' ? "::Nl::Async::Stream[#{result}]" : "::Nl::Async::Future[#{result}]"
|
|
370
|
+
yard_operation = method == 'dump' ? "Nl::Async::Stream<#{result}>" : "Nl::Async::Future<#{result}>"
|
|
371
|
+
|
|
372
|
+
emit_comment(operation_doc(oper))
|
|
373
|
+
emit_rbs_comment("(#{rbs_params}) -> #{operation}")
|
|
374
|
+
emit_yard_options(params)
|
|
375
|
+
emit_yard_return(yard_operation)
|
|
376
|
+
emit_yard_see(request_class)
|
|
377
|
+
write("def #{method.as_method_name}_#{oper.name.as_method_name}(**args)")
|
|
378
|
+
indent do
|
|
379
|
+
write("@exchange.call(#{method.as_symbol_literal}, #{request_class}, #{reply_class}, args)")
|
|
380
|
+
end
|
|
381
|
+
write('end')
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
emit_comment('Returns the asynchronous operation facade for this family.')
|
|
387
|
+
emit_rbs_comment('(?stream_capacity: ::Integer?) -> AsyncOperations')
|
|
388
|
+
emit_yard_param('stream_capacity', '::Integer, nil')
|
|
389
|
+
emit_yard_return('AsyncOperations')
|
|
390
|
+
write('def async(stream_capacity: nil)')
|
|
391
|
+
indent do
|
|
392
|
+
write('return @async_operations ||= build_async_facade(AsyncOperations) if stream_capacity.nil?')
|
|
393
|
+
write('build_async_facade(AsyncOperations, stream_capacity:)')
|
|
394
|
+
end
|
|
395
|
+
write('end')
|
|
396
|
+
|
|
397
|
+
emit_subscription_methods unless @ynl.mcast_groups.empty?
|
|
252
398
|
end
|
|
253
399
|
|
|
254
400
|
classname
|
|
@@ -291,64 +437,808 @@ module Ynl
|
|
|
291
437
|
write('end')
|
|
292
438
|
end
|
|
293
439
|
|
|
294
|
-
private def emit_const(name, value, rbs: nil)
|
|
440
|
+
private def emit_const(name, value, rbs: nil, internal: false)
|
|
441
|
+
emit_internal if internal
|
|
295
442
|
write(name, ' = ', value, *([' #: ', rbs] if rbs))
|
|
296
443
|
end
|
|
297
444
|
|
|
445
|
+
private def emit_value_definitions(namespace, definitions)
|
|
446
|
+
return if definitions.empty?
|
|
447
|
+
|
|
448
|
+
emit_module(namespace) do
|
|
449
|
+
definitions.each do |definition|
|
|
450
|
+
emit_comment(definition.doc)
|
|
451
|
+
emit_module(definition.name.as_class_name) do
|
|
452
|
+
definition.entries.each do |entry|
|
|
453
|
+
emit_comment(entry.doc)
|
|
454
|
+
emit_const(entry.name.as_class_name, yield(definition, entry))
|
|
455
|
+
end
|
|
456
|
+
end
|
|
457
|
+
end
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
private def enums_used_as_flags
|
|
462
|
+
@ynl.attribute_sets.each_value.flat_map(&:attributes).filter_map do |attribute|
|
|
463
|
+
next unless attribute.enum_as_flags && attribute.enum.is_a?(Models::Enum)
|
|
464
|
+
|
|
465
|
+
attribute.enum
|
|
466
|
+
end.uniq
|
|
467
|
+
end
|
|
468
|
+
|
|
298
469
|
private def emit_getter(name, expr)
|
|
299
470
|
write('def ', name, '; ', expr, '; end')
|
|
300
471
|
end
|
|
301
472
|
|
|
302
|
-
private def
|
|
303
|
-
write('#
|
|
473
|
+
private def emit_internal
|
|
474
|
+
write('# @private')
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
private def emit_open(raw:, default_resolver:, classname:)
|
|
478
|
+
resolver_rbs = unless raw
|
|
479
|
+
type = '^(::Nl::Genl::Client, ::String) -> ::Nl::Genl::FamilyInfo'
|
|
480
|
+
default_resolver ? "?resolver: #{type}" : "resolver: #{type}"
|
|
481
|
+
end
|
|
482
|
+
rbs_params = [
|
|
483
|
+
resolver_rbs,
|
|
484
|
+
'?executor: executor?',
|
|
485
|
+
'?notification_capacity: ::Integer?',
|
|
486
|
+
].compact.join(', ')
|
|
487
|
+
emit_rbs_comment(
|
|
488
|
+
"(#{rbs_params}) -> (::Nl::Family::Session & instance)",
|
|
489
|
+
"| [R] (#{rbs_params}) { (instance) -> R } -> R",
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
resolver_ruby = unless raw
|
|
493
|
+
default_resolver ? "resolver: #{default_resolver}" : 'resolver:'
|
|
494
|
+
end
|
|
495
|
+
ruby_params = [
|
|
496
|
+
resolver_ruby,
|
|
497
|
+
'executor: nil',
|
|
498
|
+
'notification_capacity: DEFAULT_NOTIFICATION_CAPACITY',
|
|
499
|
+
].compact.join(', ')
|
|
500
|
+
emit_yard_overload("open(#{ruby_params})") do |indentation|
|
|
501
|
+
emit_yard_open_params(resolver: !raw, indentation:)
|
|
502
|
+
emit_yard_return(classname, indentation:)
|
|
503
|
+
end
|
|
504
|
+
emit_yard_overload("open(#{ruby_params}, &block)") do |indentation|
|
|
505
|
+
emit_yard_open_params(resolver: !raw, indentation:)
|
|
506
|
+
emit_yard_yieldparam('family', classname, indentation:)
|
|
507
|
+
emit_yard_return('Object', 'the result of the block', indentation:)
|
|
508
|
+
end
|
|
509
|
+
write("def self.open(#{ruby_params})")
|
|
510
|
+
indent { write('super') }
|
|
511
|
+
write('end')
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
private def emit_yard_open_params(resolver:, indentation: '')
|
|
515
|
+
emit_yard_param('resolver', '#call', indentation:) if resolver
|
|
516
|
+
emit_yard_param('executor', 'Symbol, nil', indentation:)
|
|
517
|
+
emit_yard_param('notification_capacity', 'Integer, nil', indentation:)
|
|
304
518
|
end
|
|
305
519
|
|
|
306
520
|
private def emit_comment(comment)
|
|
307
521
|
return unless comment
|
|
522
|
+
comment = comment.gsub(KERNEL_DOC_REFERENCE) do |reference|
|
|
523
|
+
"{https://docs.kernel.org/#{$~[:path]}.html #{reference}}"
|
|
524
|
+
end
|
|
308
525
|
comment.each_line(chomp: true) do |line|
|
|
309
526
|
write('# ', line)
|
|
310
527
|
end
|
|
311
528
|
end
|
|
312
529
|
|
|
530
|
+
private def operation_doc(operation)
|
|
531
|
+
flag_docs = operation.flags.filter_map { OPERATION_FLAG_DOCS[it] }
|
|
532
|
+
[operation.doc, *flag_docs].compact.join("\n\n")
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
private def notification_operations
|
|
536
|
+
@notification_operations ||= @ynl.operations.values.select(&:notification)
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
private def notification_doc(operation)
|
|
540
|
+
group = operation.notification.group
|
|
541
|
+
[operation.doc, ("Multicast group: `#{group.name}`." if group)].compact.join("\n\n")
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
private def notification_layout(operation)
|
|
545
|
+
notification = operation.notification
|
|
546
|
+
if notification.kind == :event
|
|
547
|
+
[notification.message, operation.fixed_header, operation.attribute_set]
|
|
548
|
+
else
|
|
549
|
+
source = notification.source
|
|
550
|
+
mode = source.doit || source.dumpit
|
|
551
|
+
reply = mode&.reply
|
|
552
|
+
unless reply
|
|
553
|
+
selected = source.doit ? 'do' : 'dump'
|
|
554
|
+
raise ParseError,
|
|
555
|
+
"Notification #{operation.name.inspect} references #{source.name.inspect}, which has no #{selected} reply"
|
|
556
|
+
end
|
|
557
|
+
message = Models::Message.new(
|
|
558
|
+
value: notification.message.value,
|
|
559
|
+
attributes: reply.attributes,
|
|
560
|
+
)
|
|
561
|
+
[message, source.fixed_header, source.attribute_set]
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
private def emit_message_class(name, message, fixed_header:, attribute_set:, doc:)
|
|
566
|
+
emit_comment(doc)
|
|
567
|
+
emit_class(name, "#@wire::Message") do
|
|
568
|
+
emit_const('TYPE', message.value)
|
|
569
|
+
emit_const('FIXED_HEADER', fixed_header&.then { 'Structs::' + it.name.as_class_name } || 'nil')
|
|
570
|
+
emit_const('ATTRIBUTE_SET', "AttributeSets::#{attribute_set.name.as_class_name}")
|
|
571
|
+
attribute_params = attribute_set.attributes.map(&:name) & message.attributes
|
|
572
|
+
emit_const('ATTRIBUTES', "Ractor.make_shareable(%i[#{attribute_params.map { it.as_variable_name }.join(' ')}])")
|
|
573
|
+
fixed_header&.members&.each do |member|
|
|
574
|
+
param = member.name
|
|
575
|
+
datatype = member.type
|
|
576
|
+
next if datatype.is_a? Types::Pad
|
|
577
|
+
next if attribute_params.include?(param)
|
|
578
|
+
emit_comment(member.doc)
|
|
579
|
+
emit_rbs_comment('return: ' + struct_member_rbs_type(datatype))
|
|
580
|
+
emit_yard_return(struct_member_yard_type(datatype))
|
|
581
|
+
emit_getter(param.as_method_name, "fixed_header.#{param.as_method_name}")
|
|
582
|
+
end
|
|
583
|
+
attribute_params.each do |param|
|
|
584
|
+
attribute = attribute_set.attributes.find { it.name == param }
|
|
585
|
+
datatype = attribute.type
|
|
586
|
+
next if datatype.is_a? Types::Pad
|
|
587
|
+
extending = fixed_header&.members&.any? { it.name == param }
|
|
588
|
+
|
|
589
|
+
if extending && attribute.multi?
|
|
590
|
+
raise ParseError, "Multi-attribute #{param.inspect} conflicts with a fixed-header member"
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
emit_comment(attribute_doc(attribute))
|
|
594
|
+
emit_rbs_comment('return: ' + attribute_rbs_type(attribute))
|
|
595
|
+
emit_yard_return(attribute_yard_type(attribute))
|
|
596
|
+
emit_yard_see("AttributeSets::#{attribute_set.name.as_class_name}::#{attribute.name.as_class_name}")
|
|
597
|
+
if attribute.multi?
|
|
598
|
+
emit_getter(param.as_method_name, "attributes[#{param.as_variable_name.as_symbol_literal}].map(&:value)")
|
|
599
|
+
next
|
|
600
|
+
end
|
|
601
|
+
if extending
|
|
602
|
+
emit_getter(param.as_method_name, "attributes[#{param.as_variable_name.as_symbol_literal}]&.value || fixed_header.#{param.as_method_name}")
|
|
603
|
+
else
|
|
604
|
+
emit_getter(param.as_method_name, "attributes[#{param.as_variable_name.as_symbol_literal}]&.value")
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
private def emit_sub_messages
|
|
611
|
+
return if @ynl.sub_messages.empty?
|
|
612
|
+
|
|
613
|
+
emit_module('SubMessages') do
|
|
614
|
+
@ynl.sub_messages.each_value do |sub_message|
|
|
615
|
+
grouped = sub_message.formats.group_by { it.value.as_class_name }
|
|
616
|
+
if collision = grouped.find { |_name, formats| formats.length > 1 }
|
|
617
|
+
ruby_name, formats = collision
|
|
618
|
+
raise ParseError,
|
|
619
|
+
"Sub-message formats #{formats.map(&:value).map(&:inspect).join(' and ')} normalize to #{ruby_name.inspect}"
|
|
620
|
+
end
|
|
621
|
+
emit_module(sub_message.name.as_class_name) do
|
|
622
|
+
sub_message.formats.each do |format|
|
|
623
|
+
emit_sub_message_class(format.value.as_class_name, format)
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
end
|
|
627
|
+
end
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
private def emit_sub_message_class(name, format)
|
|
631
|
+
emit_class(name, '::Nl::SubMessage') do
|
|
632
|
+
emit_const('FIXED_HEADER', format.fixed_header&.then { 'Structs::' + it.name.as_class_name } || 'nil')
|
|
633
|
+
emit_const('ATTRIBUTE_SET', format.attribute_set&.then { 'AttributeSets::' + it.name.as_class_name } || 'nil')
|
|
634
|
+
|
|
635
|
+
format.fixed_header&.members&.each do |member|
|
|
636
|
+
next if member.type.is_a?(Types::Pad)
|
|
637
|
+
extending = format.attribute_set&.attributes&.any? { it.name == member.name }
|
|
638
|
+
next if extending
|
|
639
|
+
emit_comment(member.doc)
|
|
640
|
+
emit_rbs_comment('return: ' + struct_member_rbs_type(member.type))
|
|
641
|
+
emit_yard_return(struct_member_yard_type(member.type))
|
|
642
|
+
emit_getter(member.name.as_method_name, "fixed_header.#{member.name.as_method_name}")
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
format.attribute_set&.attributes&.each do |attribute|
|
|
646
|
+
next if attribute.type.is_a?(Types::Pad)
|
|
647
|
+
extending = format.fixed_header&.members&.any? { it.name == attribute.name }
|
|
648
|
+
emit_comment(attribute_doc(attribute))
|
|
649
|
+
emit_rbs_comment('return: ' + attribute_rbs_type(attribute))
|
|
650
|
+
emit_yard_return(attribute_yard_type(attribute))
|
|
651
|
+
emit_yard_see("AttributeSets::#{format.attribute_set.name.as_class_name}::#{attribute.name.as_class_name}")
|
|
652
|
+
expression = if attribute.multi?
|
|
653
|
+
"attributes[#{attribute.name.as_variable_name.as_symbol_literal}].map(&:value)"
|
|
654
|
+
elsif extending
|
|
655
|
+
"attributes[#{attribute.name.as_variable_name.as_symbol_literal}]&.value || fixed_header.#{attribute.name.as_method_name}"
|
|
656
|
+
else
|
|
657
|
+
"attributes[#{attribute.name.as_variable_name.as_symbol_literal}]&.value"
|
|
658
|
+
end
|
|
659
|
+
emit_getter(attribute.name.as_method_name, expression)
|
|
660
|
+
end
|
|
661
|
+
end
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
private def parameter_rbs_type(param)
|
|
665
|
+
if param.is_a?(Models::AttributeSet::Attribute)
|
|
666
|
+
type = input_rbs_type(param.type)
|
|
667
|
+
param.multi? ? "::Array[#{type}]" : type
|
|
668
|
+
else
|
|
669
|
+
struct_member_rbs_type(param.type)
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
private def parameter_yard_type(param)
|
|
674
|
+
if param.is_a?(Models::AttributeSet::Attribute)
|
|
675
|
+
type = input_yard_type(param.type)
|
|
676
|
+
param.multi? ? "Array<#{type}>" : type
|
|
677
|
+
else
|
|
678
|
+
struct_member_yard_type(param.type)
|
|
679
|
+
end
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
private def struct_member_rbs_type(type)
|
|
683
|
+
if type.is_a?(Types::Binary) && type.struct
|
|
684
|
+
'Structs::' + type.struct.name.as_class_name
|
|
685
|
+
else
|
|
686
|
+
type.rbs_type
|
|
687
|
+
end
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
private def struct_member_yard_type(type)
|
|
691
|
+
if type.is_a?(Types::Binary) && type.struct
|
|
692
|
+
'Structs::' + type.struct.name.as_class_name
|
|
693
|
+
else
|
|
694
|
+
yard_type(type)
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
private def input_rbs_type(type)
|
|
699
|
+
case type
|
|
700
|
+
when Types::NestedAttributes
|
|
701
|
+
"(#{type.rbs_type} | ::Hash[::Symbol, untyped])"
|
|
702
|
+
when Types::Binary
|
|
703
|
+
type.struct ? "(#{type.rbs_type} | ::Hash[::Symbol, untyped])" : type.rbs_type
|
|
704
|
+
when Types::PackedArray
|
|
705
|
+
"::Array[#{input_rbs_type(type.sub_type)}]"
|
|
706
|
+
when Types::NestTypeValue
|
|
707
|
+
value_type = "(AttributeSets::#{type.attribute_set.name.as_class_name} | ::Hash[::Symbol, untyped])"
|
|
708
|
+
type.type_values.length.times { value_type = "::Hash[::Integer, #{value_type}]" }
|
|
709
|
+
value_type
|
|
710
|
+
when Types::IndexedArray
|
|
711
|
+
"::Array[#{input_rbs_type(type.sub_type)}]"
|
|
712
|
+
when Types::SubMessage
|
|
713
|
+
"(#{sub_message_rbs_type(type)} | ::Hash[::Symbol, untyped])"
|
|
714
|
+
else
|
|
715
|
+
type.rbs_type
|
|
716
|
+
end
|
|
717
|
+
end
|
|
718
|
+
|
|
719
|
+
private def input_yard_type(type)
|
|
720
|
+
case type
|
|
721
|
+
when Types::NestedAttributes
|
|
722
|
+
"#{yard_type(type)}, Hash<Symbol, Object>"
|
|
723
|
+
when Types::Binary
|
|
724
|
+
type.struct ? "#{yard_type(type)}, Hash<Symbol, Object>" : yard_type(type)
|
|
725
|
+
when Types::PackedArray, Types::IndexedArray
|
|
726
|
+
"Array<#{input_yard_type(type.sub_type)}>"
|
|
727
|
+
when Types::NestTypeValue
|
|
728
|
+
value_type = "AttributeSets::#{type.attribute_set.name.as_class_name}, Hash<Symbol, Object>"
|
|
729
|
+
type.type_values.length.times { value_type = "Hash<Integer, #{value_type}>" }
|
|
730
|
+
value_type
|
|
731
|
+
when Types::SubMessage
|
|
732
|
+
"#{sub_message_yard_type(type)}, Hash<Symbol, Object>"
|
|
733
|
+
else
|
|
734
|
+
yard_type(type)
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
private def attribute_rbs_type(attribute)
|
|
739
|
+
type = if attribute.type.is_a?(Types::SubMessage)
|
|
740
|
+
sub_message_rbs_type(attribute.type)
|
|
741
|
+
else
|
|
742
|
+
attribute.type.rbs_type
|
|
743
|
+
end
|
|
744
|
+
attribute.multi? ? "::Array[#{type}]" : type
|
|
745
|
+
end
|
|
746
|
+
|
|
747
|
+
private def attribute_yard_type(attribute)
|
|
748
|
+
type = if attribute.type.is_a?(Types::SubMessage)
|
|
749
|
+
sub_message_yard_type(attribute.type)
|
|
750
|
+
else
|
|
751
|
+
yard_type(attribute.type)
|
|
752
|
+
end
|
|
753
|
+
attribute.multi? ? "Array<#{type}>" : type
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
private def attribute_doc(attribute)
|
|
757
|
+
return attribute.doc unless attribute.enum
|
|
758
|
+
|
|
759
|
+
definition = "{#{attribute_value_definition(attribute)}}"
|
|
760
|
+
value_doc = if attribute.enum_as_flags || attribute.enum.is_a?(Models::Flags)
|
|
761
|
+
"Known flags are defined in #{definition}."
|
|
762
|
+
else
|
|
763
|
+
"Known enum values are defined in #{definition}."
|
|
764
|
+
end
|
|
765
|
+
[attribute.doc, value_doc].compact.reject(&:empty?).join("\n\n")
|
|
766
|
+
end
|
|
767
|
+
|
|
768
|
+
private def attribute_value_definition(attribute)
|
|
769
|
+
namespace = if attribute.enum_as_flags || attribute.enum.is_a?(Models::Flags)
|
|
770
|
+
'Flags'
|
|
771
|
+
else
|
|
772
|
+
'Enums'
|
|
773
|
+
end
|
|
774
|
+
"#{namespace}::#{attribute.enum.name.as_class_name}"
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
private def sub_message_rbs_type(type)
|
|
778
|
+
formats = type.sub_message.formats.map do |format|
|
|
779
|
+
"SubMessages::#{type.sub_message.name.as_class_name}::#{format.value.as_class_name}"
|
|
780
|
+
end
|
|
781
|
+
(formats << '::Nl::RawSubMessage').join(' | ')
|
|
782
|
+
end
|
|
783
|
+
|
|
784
|
+
private def sub_message_yard_type(type)
|
|
785
|
+
formats = type.sub_message.formats.map do |format|
|
|
786
|
+
"SubMessages::#{type.sub_message.name.as_class_name}::#{format.value.as_class_name}"
|
|
787
|
+
end
|
|
788
|
+
(formats << 'Nl::RawSubMessage').join(', ')
|
|
789
|
+
end
|
|
790
|
+
|
|
791
|
+
private def yard_type(type)
|
|
792
|
+
case type
|
|
793
|
+
when Types::Scalar, Types::Flag
|
|
794
|
+
'Integer'
|
|
795
|
+
when Types::String
|
|
796
|
+
'String'
|
|
797
|
+
when Types::Binary
|
|
798
|
+
type.struct ? 'Structs::' + type.struct.name.as_class_name : 'String'
|
|
799
|
+
when Types::PackedArray, Types::IndexedArray
|
|
800
|
+
"Array<#{yard_type(type.sub_type)}>"
|
|
801
|
+
when Types::NestedAttributes
|
|
802
|
+
'AttributeSets::' + type.attribute_set.name.as_class_name
|
|
803
|
+
when Types::NestTypeValue
|
|
804
|
+
value_type = 'AttributeSets::' + type.attribute_set.name.as_class_name
|
|
805
|
+
type.type_values.length.times { value_type = "Hash<Integer, #{value_type}>" }
|
|
806
|
+
value_type
|
|
807
|
+
when Types::SubMessage
|
|
808
|
+
'Nl::SubMessage'
|
|
809
|
+
when Types::Pad
|
|
810
|
+
'nil'
|
|
811
|
+
when Types::Bitfield32
|
|
812
|
+
'Nl::Bitfield32'
|
|
813
|
+
else
|
|
814
|
+
raise "Unknown YARD type: #{type.class}"
|
|
815
|
+
end
|
|
816
|
+
end
|
|
817
|
+
|
|
818
|
+
private def emit_mcast_groups
|
|
819
|
+
groups = @ynl.mcast_groups.values
|
|
820
|
+
grouped = groups.group_by { it.name.as_variable_name }
|
|
821
|
+
if collision = grouped.find { |_name, matches| matches.length > 1 }
|
|
822
|
+
ruby_name, matches = collision
|
|
823
|
+
raise ParseError,
|
|
824
|
+
"Multicast groups #{matches.map(&:name).map(&:inspect).join(' and ')} normalize to #{ruby_name.to_sym.inspect}"
|
|
825
|
+
end
|
|
826
|
+
|
|
827
|
+
entries = groups.map do |group|
|
|
828
|
+
key = group.name.as_variable_name.as_symbol_literal
|
|
829
|
+
value = "::Nl::McastGroup.new(#{group.name.as_string_literal}, #{mcast_group_value_literal(group.value)})"
|
|
830
|
+
"#{key} => #{value}"
|
|
831
|
+
end
|
|
832
|
+
emit_const(
|
|
833
|
+
'MCAST_GROUPS',
|
|
834
|
+
"Ractor.make_shareable({#{entries.join(', ')}})",
|
|
835
|
+
rbs: 'Hash[::Symbol, ::Nl::McastGroup]',
|
|
836
|
+
internal: true,
|
|
837
|
+
)
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
private def emit_subscription_methods
|
|
841
|
+
groups = @ynl.mcast_groups.values
|
|
842
|
+
.map { ":#{it.name.as_variable_name}" }
|
|
843
|
+
emit_rbs_comment("(*(#{groups.join(' | ')}) groups) -> self")
|
|
844
|
+
emit_yard_param('groups', "Array<#{groups.join(', ')}>")
|
|
845
|
+
emit_yard_return('self')
|
|
846
|
+
write('def subscribe(*groups) = super')
|
|
847
|
+
emit_rbs_comment("(*(#{groups.join(' | ')}) groups) -> self")
|
|
848
|
+
emit_yard_param('groups', "Array<#{groups.join(', ')}>")
|
|
849
|
+
emit_yard_return('self')
|
|
850
|
+
write('def unsubscribe(*groups) = super')
|
|
851
|
+
end
|
|
852
|
+
|
|
313
853
|
private def emit_rbs_comment(*args)
|
|
314
|
-
write('#--')
|
|
315
854
|
args.each do |arg|
|
|
316
855
|
emit_comment('@rbs ' + arg)
|
|
317
856
|
end
|
|
318
857
|
end
|
|
319
858
|
|
|
320
|
-
private def
|
|
859
|
+
private def emit_yard_overload(signature)
|
|
860
|
+
emit_comment("@overload #{signature}")
|
|
861
|
+
yield ' '
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
private def emit_yard_options(params, indentation: '')
|
|
865
|
+
emit_yard_param('args', 'Hash', indentation:)
|
|
866
|
+
params.each do |param|
|
|
867
|
+
emit_yard_option(
|
|
868
|
+
'args',
|
|
869
|
+
param.name.as_variable_name,
|
|
870
|
+
parameter_yard_type(param),
|
|
871
|
+
param.is_a?(Models::AttributeSet::Attribute) ? attribute_doc(param) : param.doc,
|
|
872
|
+
indentation:,
|
|
873
|
+
)
|
|
874
|
+
end
|
|
875
|
+
end
|
|
876
|
+
|
|
877
|
+
private def emit_yard_option(parameter, name, type, description, indentation: '')
|
|
878
|
+
lines = description&.each_line(chomp: true)&.to_a || []
|
|
879
|
+
emit_comment(["#{indentation}@option #{parameter} [#{type}] #{name}", lines.shift].compact.join(' '))
|
|
880
|
+
lines.each do |line|
|
|
881
|
+
emit_comment("#{indentation} #{line}")
|
|
882
|
+
end
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
private def emit_yard_attribute(name, type, description)
|
|
886
|
+
emit_comment("@!attribute [rw] #{name}")
|
|
887
|
+
description&.each_line(chomp: true) do |line|
|
|
888
|
+
emit_comment(" #{line}")
|
|
889
|
+
end
|
|
890
|
+
emit_comment(" @return [#{type}]")
|
|
891
|
+
end
|
|
892
|
+
|
|
893
|
+
private def emit_yard_param(name, type, indentation: '')
|
|
894
|
+
emit_comment("#{indentation}@param [#{type}] #{name}")
|
|
895
|
+
end
|
|
896
|
+
|
|
897
|
+
private def emit_yard_return(type, description = nil, indentation: '')
|
|
898
|
+
emit_comment(["#{indentation}@return [#{type}]", description].compact.join(' '))
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
private def emit_yard_see(object)
|
|
902
|
+
emit_comment("@see #{object}")
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
private def emit_yard_yieldparam(name, type, indentation: '')
|
|
906
|
+
emit_comment("#{indentation}@yieldparam [#{type}] #{name}")
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
private def build_selector_plan
|
|
910
|
+
sets = @ynl.attribute_sets.values
|
|
911
|
+
@local_selectors = sets.to_h { [it, []] }
|
|
912
|
+
@external_selectors = sets.to_h { [it, []] }
|
|
913
|
+
|
|
914
|
+
@ynl.sub_messages.each_value do |sub_message|
|
|
915
|
+
duplicate = sub_message.formats.group_by(&:value).find { |_value, formats| formats.length > 1 }
|
|
916
|
+
if duplicate
|
|
917
|
+
raise ParseError,
|
|
918
|
+
"Duplicate format value #{duplicate.first.inspect} in sub-message #{sub_message.name.inspect}"
|
|
919
|
+
end
|
|
920
|
+
end
|
|
921
|
+
|
|
922
|
+
changed = true
|
|
923
|
+
while changed
|
|
924
|
+
changed = false
|
|
925
|
+
sets.each do |set|
|
|
926
|
+
set.attributes.each do |attribute|
|
|
927
|
+
if attribute.type.is_a?(Types::SubMessage)
|
|
928
|
+
changed |= require_selector(set, attribute.type.selector)
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
child_attribute_sets(attribute.type).each do |child|
|
|
932
|
+
@external_selectors.fetch(child).each do |selector|
|
|
933
|
+
changed |= require_selector(set, selector)
|
|
934
|
+
end
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
end
|
|
938
|
+
end
|
|
939
|
+
|
|
940
|
+
build_attribute_orders
|
|
941
|
+
roots = @ynl.operations.values.filter_map(&:attribute_set).uniq
|
|
942
|
+
roots.each do |root|
|
|
943
|
+
required = @external_selectors.fetch(root)
|
|
944
|
+
next if required.empty?
|
|
945
|
+
raise ParseError,
|
|
946
|
+
"Root attribute set #{root.name.inspect} requires external selectors: #{required.join(', ')}"
|
|
947
|
+
end
|
|
948
|
+
build_selector_sources(roots)
|
|
949
|
+
validate_selector_values
|
|
950
|
+
end
|
|
951
|
+
|
|
952
|
+
private def require_selector(set, selector)
|
|
953
|
+
local = attribute_by_name(set, selector)
|
|
954
|
+
if local&.multi?
|
|
955
|
+
raise ParseError,
|
|
956
|
+
"Multi-attribute #{selector.inspect} cannot be a selector in attribute set #{set.name.inspect}"
|
|
957
|
+
end
|
|
958
|
+
target = local ? @local_selectors : @external_selectors
|
|
959
|
+
selectors = target.fetch(set)
|
|
960
|
+
return false if selectors.include?(selector)
|
|
961
|
+
selectors << selector
|
|
962
|
+
true
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
private def build_attribute_orders
|
|
966
|
+
@attribute_orders = {}
|
|
967
|
+
@ynl.attribute_sets.each_value do |set|
|
|
968
|
+
dependencies = set.attributes.to_h { |attribute| [attribute, []] }
|
|
969
|
+
set.attributes.each do |attribute|
|
|
970
|
+
selectors = []
|
|
971
|
+
selectors << attribute.type.selector if attribute.type.is_a?(Types::SubMessage)
|
|
972
|
+
child_attribute_sets(attribute.type).each do |child|
|
|
973
|
+
selectors.concat(@external_selectors.fetch(child))
|
|
974
|
+
end
|
|
975
|
+
|
|
976
|
+
selectors.uniq.each do |selector|
|
|
977
|
+
selector_attribute = attribute_by_name(set, selector)
|
|
978
|
+
dependencies.fetch(attribute) << selector_attribute if selector_attribute
|
|
979
|
+
end
|
|
980
|
+
end
|
|
981
|
+
|
|
982
|
+
ordered = []
|
|
983
|
+
remaining = set.attributes.dup
|
|
984
|
+
until remaining.empty?
|
|
985
|
+
index = remaining.index do |attribute|
|
|
986
|
+
dependencies.fetch(attribute).all? { ordered.include?(it) }
|
|
987
|
+
end
|
|
988
|
+
unless index
|
|
989
|
+
raise ParseError,
|
|
990
|
+
"Selector dependency cycle in attribute set #{set.name.inspect}: #{remaining.map(&:name).join(', ')}"
|
|
991
|
+
end
|
|
992
|
+
ordered << remaining.delete_at(index)
|
|
993
|
+
end
|
|
994
|
+
@attribute_orders[set] = ordered.each_with_index.to_h
|
|
995
|
+
end
|
|
996
|
+
end
|
|
997
|
+
|
|
998
|
+
private def build_selector_sources(roots)
|
|
999
|
+
@selector_sources = @ynl.attribute_sets.values.to_h { [it, Hash.new { |h, k| h[k] = [] }] }
|
|
1000
|
+
reached = roots.to_h { [it, true] }
|
|
1001
|
+
|
|
1002
|
+
changed = true
|
|
1003
|
+
while changed
|
|
1004
|
+
changed = false
|
|
1005
|
+
reached.keys.each do |set|
|
|
1006
|
+
set.attributes.each do |attribute|
|
|
1007
|
+
child_attribute_sets(attribute.type).each do |child|
|
|
1008
|
+
unless reached[child]
|
|
1009
|
+
reached[child] = true
|
|
1010
|
+
changed = true
|
|
1011
|
+
end
|
|
1012
|
+
@external_selectors.fetch(child).each do |selector|
|
|
1013
|
+
sources = if source = attribute_by_name(set, selector)
|
|
1014
|
+
[source]
|
|
1015
|
+
else
|
|
1016
|
+
@selector_sources.fetch(set)[selector]
|
|
1017
|
+
end
|
|
1018
|
+
target = @selector_sources.fetch(child)[selector]
|
|
1019
|
+
sources.each do |candidate|
|
|
1020
|
+
unless target.include?(candidate)
|
|
1021
|
+
target << candidate
|
|
1022
|
+
changed = true
|
|
1023
|
+
end
|
|
1024
|
+
end
|
|
1025
|
+
end
|
|
1026
|
+
end
|
|
1027
|
+
end
|
|
1028
|
+
end
|
|
1029
|
+
end
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
private def validate_selector_values
|
|
1033
|
+
@ynl.attribute_sets.each_value do |set|
|
|
1034
|
+
set.attributes.each do |attribute|
|
|
1035
|
+
next unless attribute.type.is_a?(Types::SubMessage)
|
|
1036
|
+
keys = attribute.type.sub_message.formats.map do |format|
|
|
1037
|
+
selector_value_literal(set, attribute.type.selector, format.value)
|
|
1038
|
+
end
|
|
1039
|
+
duplicate = keys.group_by(&:itself).find { |_key, matches| matches.length > 1 }
|
|
1040
|
+
if duplicate
|
|
1041
|
+
raise ParseError,
|
|
1042
|
+
"Sub-message #{attribute.type.sub_message.name.inspect} has duplicate compiled selector value #{duplicate.first}"
|
|
1043
|
+
end
|
|
1044
|
+
end
|
|
1045
|
+
end
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
private def child_attribute_sets(type)
|
|
1049
|
+
case type
|
|
1050
|
+
when Types::NestedAttributes, Types::NestTypeValue
|
|
1051
|
+
[type.attribute_set]
|
|
1052
|
+
when Types::IndexedArray
|
|
1053
|
+
child_attribute_sets(type.sub_type)
|
|
1054
|
+
when Types::SubMessage
|
|
1055
|
+
type.sub_message.formats.filter_map(&:attribute_set)
|
|
1056
|
+
else
|
|
1057
|
+
[]
|
|
1058
|
+
end
|
|
1059
|
+
end
|
|
1060
|
+
|
|
1061
|
+
private def attribute_by_name(set, name)
|
|
1062
|
+
set.attributes.find { it.name == name }
|
|
1063
|
+
end
|
|
1064
|
+
|
|
1065
|
+
private def selector_source_literal(set, selector)
|
|
1066
|
+
if (index = @local_selectors.fetch(set).index(selector))
|
|
1067
|
+
"::Nl::Selector::Local.new(#{index})"
|
|
1068
|
+
elsif (index = @external_selectors.fetch(set).index(selector))
|
|
1069
|
+
"::Nl::Selector::External.new(#{index})"
|
|
1070
|
+
else
|
|
1071
|
+
raise ParseError, "Unresolved selector #{selector.inspect} in attribute set #{set.name.inspect}"
|
|
1072
|
+
end
|
|
1073
|
+
end
|
|
1074
|
+
|
|
1075
|
+
private def selector_bindings_literal(parent, child)
|
|
1076
|
+
return '[]' unless child
|
|
1077
|
+
bindings = @external_selectors.fetch(child).map do |selector|
|
|
1078
|
+
selector_source_literal(parent, selector)
|
|
1079
|
+
end
|
|
1080
|
+
"Ractor.make_shareable([#{bindings.join(', ')}])"
|
|
1081
|
+
end
|
|
1082
|
+
|
|
1083
|
+
private def selector_value_literal(set, selector, format_value)
|
|
1084
|
+
sources = if source = attribute_by_name(set, selector)
|
|
1085
|
+
[source]
|
|
1086
|
+
else
|
|
1087
|
+
@selector_sources.fetch(set)[selector]
|
|
1088
|
+
end
|
|
1089
|
+
if sources.empty?
|
|
1090
|
+
raise ParseError,
|
|
1091
|
+
"Cannot determine the type of external selector #{selector.inspect} in attribute set #{set.name.inspect}"
|
|
1092
|
+
end
|
|
1093
|
+
|
|
1094
|
+
values = sources.map do |source|
|
|
1095
|
+
case source.type
|
|
1096
|
+
when Types::String
|
|
1097
|
+
format_value.as_string_literal
|
|
1098
|
+
when Types::Scalar
|
|
1099
|
+
unless source.enum.is_a?(Models::Enum)
|
|
1100
|
+
raise ParseError,
|
|
1101
|
+
"Integer selector #{source.name.inspect} must reference an enum"
|
|
1102
|
+
end
|
|
1103
|
+
entry = source.enum.entries.find { it.name == format_value }
|
|
1104
|
+
unless entry
|
|
1105
|
+
raise ParseError,
|
|
1106
|
+
"Selector enum #{source.enum.name.inspect} has no entry #{format_value.inspect}"
|
|
1107
|
+
end
|
|
1108
|
+
entry.value.to_s
|
|
1109
|
+
else
|
|
1110
|
+
raise ParseError,
|
|
1111
|
+
"Selector #{source.name.inspect} must be a string or an enum-backed integer"
|
|
1112
|
+
end
|
|
1113
|
+
end.uniq
|
|
1114
|
+
if values.length != 1
|
|
1115
|
+
raise ParseError,
|
|
1116
|
+
"Selector #{selector.inspect} has incompatible definitions for format #{format_value.inspect}"
|
|
1117
|
+
end
|
|
1118
|
+
values.first
|
|
1119
|
+
end
|
|
1120
|
+
|
|
1121
|
+
private def to_datatype(type, checks, owner: nil)
|
|
321
1122
|
case type
|
|
322
1123
|
when Types::Pad
|
|
323
|
-
"
|
|
1124
|
+
"::Nl::DataTypes::Pad.new(#{type.length})"
|
|
324
1125
|
when Types::Flag
|
|
325
|
-
"
|
|
1126
|
+
"::Nl::DataTypes::Flag.new"
|
|
326
1127
|
when Types::Bitfield32
|
|
327
|
-
"
|
|
1128
|
+
"::Nl::DataTypes::Bitfield32.new"
|
|
328
1129
|
when Types::Scalar
|
|
329
|
-
"
|
|
1130
|
+
byte_order = "::Nl::Endian::#{type.byte_order.name.as_class_name}"
|
|
1131
|
+
if ['sint', 'uint'].include?(type.type)
|
|
1132
|
+
"::Nl::DataTypes::VariableInteger.new(#{byte_order}, signed: #{type.type == 'sint'}, check: #{to_checks(checks)})"
|
|
1133
|
+
else
|
|
1134
|
+
"::Nl::DataTypes::Scalar.new(#{byte_order}::#{type.type.as_const_name}, check: #{to_checks(checks)})"
|
|
1135
|
+
end
|
|
330
1136
|
when Types::String
|
|
331
|
-
"
|
|
1137
|
+
"::Nl::DataTypes::String.new(check: #{to_checks(checks)})"
|
|
332
1138
|
when Types::Binary
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
1139
|
+
if type.struct
|
|
1140
|
+
"::Nl::DataTypes::Struct.new(Structs::#{type.struct.name.as_class_name}, check: #{to_checks(checks)}, consume_remaining: true)"
|
|
1141
|
+
else
|
|
1142
|
+
"::Nl::DataTypes::Binary.new(check: #{to_checks(checks)})"
|
|
1143
|
+
end
|
|
1144
|
+
when Types::PackedArray
|
|
1145
|
+
"::Nl::DataTypes::PackedArray.new(#{to_datatype(type.sub_type, nil)}, check: #{to_checks(checks)})"
|
|
338
1146
|
when Types::NestedAttributes
|
|
339
|
-
|
|
1147
|
+
bindings = selector_bindings_literal(owner, type.attribute_set)
|
|
1148
|
+
"::Nl::DataTypes::NestedAttributes.new(AttributeSets::#{type.attribute_set.name.as_class_name}, selector_bindings: #{bindings})"
|
|
1149
|
+
when Types::NestTypeValue
|
|
1150
|
+
bindings = selector_bindings_literal(owner, type.attribute_set)
|
|
1151
|
+
"::Nl::DataTypes::NestTypeValue.new(AttributeSets::#{type.attribute_set.name.as_class_name}, #{type.type_values.length}, selector_bindings: #{bindings})"
|
|
340
1152
|
when Types::IndexedArray
|
|
341
|
-
"
|
|
1153
|
+
"::Nl::DataTypes::IndexedArray.new(#{to_datatype(type.sub_type, nil, owner:)})"
|
|
342
1154
|
when Types::SubMessage
|
|
343
|
-
|
|
1155
|
+
selector = selector_source_literal(owner, type.selector)
|
|
1156
|
+
formats = type.sub_message.formats.map do |format|
|
|
1157
|
+
key = selector_value_literal(owner, type.selector, format.value)
|
|
1158
|
+
klass = "SubMessages::#{type.sub_message.name.as_class_name}::#{format.value.as_class_name}"
|
|
1159
|
+
bindings = selector_bindings_literal(owner, format.attribute_set)
|
|
1160
|
+
nested = !format.attribute_set.nil?
|
|
1161
|
+
"#{key} => ::Nl::DataTypes::SubMessage::Format.new(#{klass}, #{bindings}, #{nested})"
|
|
1162
|
+
end
|
|
1163
|
+
"::Nl::DataTypes::SubMessage.new(#{selector}, {#{formats.join(', ')}})"
|
|
344
1164
|
else
|
|
345
1165
|
raise "Unknown type: #{type.class}"
|
|
346
1166
|
end
|
|
347
1167
|
end
|
|
348
1168
|
|
|
1169
|
+
private def to_struct_member_datatype(type)
|
|
1170
|
+
if type.is_a?(Types::Binary) && type.struct
|
|
1171
|
+
"::Nl::DataTypes::Struct.new(Structs::#{type.struct.name.as_class_name}, check: nil)"
|
|
1172
|
+
elsif type.is_a?(Types::Binary)
|
|
1173
|
+
"::Nl::DataTypes::Binary.new(length: #{type.length}, check: nil)"
|
|
1174
|
+
else
|
|
1175
|
+
to_datatype(type, nil)
|
|
1176
|
+
end
|
|
1177
|
+
end
|
|
1178
|
+
|
|
349
1179
|
private def to_checks(checks)
|
|
350
1180
|
return 'nil' if !checks || checks.empty?
|
|
351
|
-
%Q{-> { #{checks.join('; ')} }}
|
|
1181
|
+
%Q{-> { #{checks.map { to_check(it) }.join('; ')} }}
|
|
1182
|
+
end
|
|
1183
|
+
|
|
1184
|
+
private def to_check(check)
|
|
1185
|
+
value = case check.value
|
|
1186
|
+
when Models::Const
|
|
1187
|
+
"Constants::#{check.value.name.as_const_name}"
|
|
1188
|
+
else
|
|
1189
|
+
integer_literal(check.value)
|
|
1190
|
+
end
|
|
1191
|
+
|
|
1192
|
+
comparison, description = case check.operation
|
|
1193
|
+
when 'max'
|
|
1194
|
+
["it <= #{value}", "greater than maximum #{check_message_value(check, value)}"]
|
|
1195
|
+
when 'min'
|
|
1196
|
+
["it >= #{value}", "less than minimum #{check_message_value(check, value)}"]
|
|
1197
|
+
when 'min-len'
|
|
1198
|
+
["it.bytesize >= #{value}", "shorter than minimum length #{check_message_value(check, value)}"]
|
|
1199
|
+
when 'max-len'
|
|
1200
|
+
["it.bytesize <= #{value}", "longer than maximum length #{check_message_value(check, value)}"]
|
|
1201
|
+
when 'exact-len'
|
|
1202
|
+
["it.bytesize == #{value}", "not equal to length #{check_message_value(check, value)}"]
|
|
1203
|
+
else
|
|
1204
|
+
raise "Unknown check: #{check.operation}"
|
|
1205
|
+
end
|
|
1206
|
+
|
|
1207
|
+
%Q{raise ArgumentError, "Value \#{it.inspect} is #{description}" unless #{comparison}}
|
|
1208
|
+
end
|
|
1209
|
+
|
|
1210
|
+
private def check_message_value(check, value)
|
|
1211
|
+
check.value.is_a?(Models::Const) ? "\#{#{value}}" : value
|
|
1212
|
+
end
|
|
1213
|
+
|
|
1214
|
+
private def const_value_literal(value)
|
|
1215
|
+
case value
|
|
1216
|
+
when Integer
|
|
1217
|
+
value.to_s
|
|
1218
|
+
when String
|
|
1219
|
+
value.as_string_literal
|
|
1220
|
+
else
|
|
1221
|
+
raise ParseError, "YNL constant value must be a string or an integer, got #{value.class}"
|
|
1222
|
+
end
|
|
1223
|
+
end
|
|
1224
|
+
|
|
1225
|
+
private def integer_literal(value)
|
|
1226
|
+
unless value.is_a?(Integer)
|
|
1227
|
+
raise ParseError, "YNL check value must be an integer, got #{value.class}"
|
|
1228
|
+
end
|
|
1229
|
+
|
|
1230
|
+
value.to_s
|
|
1231
|
+
end
|
|
1232
|
+
|
|
1233
|
+
private def mcast_group_value_literal(value)
|
|
1234
|
+
case value
|
|
1235
|
+
when Integer
|
|
1236
|
+
value.to_s
|
|
1237
|
+
when nil
|
|
1238
|
+
'nil'
|
|
1239
|
+
else
|
|
1240
|
+
raise ParseError, "YNL multicast group value must be an integer, got #{value.class}"
|
|
1241
|
+
end
|
|
352
1242
|
end
|
|
353
1243
|
end
|
|
354
1244
|
end
|