ynl 0.3.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/Rakefile +7 -0
- data/lib/ynl/family.rb +15 -2
- data/lib/ynl/generator.rb +811 -64
- data/lib/ynl/models.rb +21 -10
- data/lib/ynl/parser.rb +109 -42
- data/lib/ynl/version.rb +1 -1
- data/lib/ynl.rb +32 -4
- 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 +10 -4
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,7 +34,6 @@ 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
|
|
@@ -41,16 +42,22 @@ module Ynl
|
|
|
41
42
|
'admin-perm' => 'Requires CAP_NET_ADMIN in the initial user namespace.',
|
|
42
43
|
'uns-admin-perm' => 'Requires CAP_NET_ADMIN in the user namespace owning the network namespace.',
|
|
43
44
|
}.freeze
|
|
44
|
-
|
|
45
|
+
KERNEL_DOC_REFERENCE = %r{Documentation/(?<path>[A-Za-z0-9_./+-]+)\.rst\b}
|
|
46
|
+
private_constant :OPERATION_FLAG_DOCS, :KERNEL_DOC_REFERENCE
|
|
45
47
|
|
|
48
|
+
# @rbs (Models::Family ynl, _Writable out) -> void
|
|
46
49
|
def initialize(ynl, out)
|
|
47
50
|
@ynl = ynl
|
|
48
51
|
@out = out
|
|
49
52
|
@indent = 0
|
|
50
53
|
end
|
|
51
54
|
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
54
61
|
|
|
55
62
|
emit_comment(@ynl.doc)
|
|
56
63
|
|
|
@@ -58,37 +65,62 @@ module Ynl
|
|
|
58
65
|
emit_class([*namespace, classname].join('::'), superclass) do
|
|
59
66
|
emit_const('NAME', @ynl.name.as_string_literal)
|
|
60
67
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
emit_const('PROTOCOL', "Ractor.make_shareable(::Nl::Protocols::Genl.new(#{@ynl.name.as_string_literal}))")
|
|
65
|
-
end
|
|
68
|
+
emit_const('PROTONUM', @ynl.protonum) if raw
|
|
69
|
+
emit_const('VERSION', @ynl.version) unless raw
|
|
70
|
+
emit_open(raw:, default_resolver:, classname:)
|
|
66
71
|
|
|
67
72
|
emit_mcast_groups
|
|
68
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
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
deferred_struct_consts = []
|
|
69
89
|
emit_module('Structs') do
|
|
70
90
|
@ynl.structs.each do |name, struct|
|
|
71
91
|
emit_comment(struct.doc)
|
|
72
92
|
write(name.as_class_name, " = Struct.new(")
|
|
73
93
|
indent do
|
|
74
94
|
struct.members.each do |member|
|
|
75
|
-
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))
|
|
76
96
|
end
|
|
77
97
|
end
|
|
78
98
|
write(')')
|
|
79
99
|
emit_class(name.as_class_name) do
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
86
116
|
|
|
87
117
|
emit_comment('Decodes the struct.')
|
|
88
118
|
emit_rbs_comment(
|
|
89
119
|
'decoder: ::Nl::Decoder',
|
|
90
120
|
'return: instance',
|
|
91
121
|
)
|
|
122
|
+
emit_yard_param('decoder', 'Nl::Decoder')
|
|
123
|
+
emit_yard_return('self')
|
|
92
124
|
write('def self.decode(decoder)')
|
|
93
125
|
indent do
|
|
94
126
|
write('self.new(*MEMBERS.map {|name, datatype| datatype.decode(decoder) })')
|
|
@@ -100,6 +132,8 @@ module Ynl
|
|
|
100
132
|
'encoder: ::Nl::Encoder',
|
|
101
133
|
'return: void',
|
|
102
134
|
)
|
|
135
|
+
emit_yard_param('encoder', 'Nl::Encoder')
|
|
136
|
+
emit_yard_return('void')
|
|
103
137
|
write('def encode(encoder)')
|
|
104
138
|
indent do
|
|
105
139
|
write('MEMBERS.each {|name, datatype| datatype.encode(encoder, self.public_send(name)) }')
|
|
@@ -108,50 +142,91 @@ module Ynl
|
|
|
108
142
|
end
|
|
109
143
|
end
|
|
110
144
|
end
|
|
145
|
+
deferred_struct_consts.each do |name, members|
|
|
146
|
+
emit_const(name, members, rbs: 'Hash[::Symbol, ::Nl::_DataType]', internal: true)
|
|
147
|
+
end
|
|
111
148
|
|
|
149
|
+
deferred_sub_message_datatypes = []
|
|
112
150
|
emit_module('AttributeSets') do
|
|
113
151
|
deferred_consts = []
|
|
114
152
|
@ynl.attribute_sets.each do |name, attr_set|
|
|
115
153
|
emit_comment(attr_set.doc)
|
|
116
|
-
emit_class(name.as_class_name,
|
|
154
|
+
emit_class(name.as_class_name, '::Nl::AttributeSet') do
|
|
117
155
|
emit_comment("Abstract class")
|
|
118
|
-
emit_class('Attribute',
|
|
156
|
+
emit_class('Attribute', '::Nl::AttributeSet::Attribute') do
|
|
119
157
|
end
|
|
120
158
|
attr_set.attributes.each do |attr|
|
|
121
|
-
emit_comment(attr
|
|
159
|
+
emit_comment(attribute_doc(attr))
|
|
122
160
|
emit_class(attr.name.as_class_name, 'Attribute') do
|
|
123
161
|
emit_const('TYPE', attr.value)
|
|
124
162
|
emit_const('NAME', attr.name.as_variable_name.as_symbol_literal)
|
|
125
|
-
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) ||
|
|
126
174
|
attr.type.is_a?(Types::NestTypeValue) ||
|
|
127
175
|
(attr.type.is_a?(Types::IndexedArray) && attr.type.sub_type.is_a?(Types::NestedAttributes))
|
|
128
|
-
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)]
|
|
129
177
|
else
|
|
130
|
-
emit_const('DATATYPE', to_datatype(attr.type, attr.checks))
|
|
178
|
+
emit_const('DATATYPE', to_datatype(attr.type, attr.checks, owner: attr_set), internal: true)
|
|
131
179
|
end
|
|
132
180
|
end
|
|
133
181
|
end
|
|
134
182
|
|
|
135
|
-
|
|
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
|
+
|
|
136
193
|
emit_const(
|
|
137
194
|
'BY_NAME',
|
|
138
195
|
"Ractor.make_shareable({#{attr_set.attributes.map { "#{it.name.as_variable_name.as_symbol_literal} => #{it.name.as_class_name}" }.join(', ') }})",
|
|
139
196
|
rbs: 'Hash[::Symbol, Attribute]',
|
|
197
|
+
internal: true,
|
|
140
198
|
)
|
|
141
199
|
|
|
142
|
-
emit_nodoc
|
|
143
200
|
emit_const(
|
|
144
201
|
'BY_TYPE',
|
|
145
202
|
"Ractor.make_shareable({#{attr_set.attributes.map { "#{it.value} => #{it.name.as_class_name}" }.join(', ') }})",
|
|
146
203
|
rbs: 'Hash[::Integer, Attribute]',
|
|
204
|
+
internal: true,
|
|
147
205
|
)
|
|
148
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
|
+
|
|
149
222
|
emit_singleton_class do
|
|
150
223
|
emit_comment('Looks up Attribute class by name.')
|
|
151
224
|
emit_rbs_comment(
|
|
152
225
|
'name: Symbol',
|
|
153
226
|
'return: Attribute',
|
|
154
227
|
)
|
|
228
|
+
emit_yard_param('name', 'Symbol')
|
|
229
|
+
emit_yard_return('Attribute')
|
|
155
230
|
emit_getter('by_name(name)', 'BY_NAME.fetch(name)')
|
|
156
231
|
|
|
157
232
|
emit_comment('Looks up Attribute class by type value.')
|
|
@@ -159,11 +234,21 @@ module Ynl
|
|
|
159
234
|
'type: Integer',
|
|
160
235
|
'return: Attribute',
|
|
161
236
|
)
|
|
237
|
+
emit_yard_param('type', 'Integer')
|
|
238
|
+
emit_yard_return('Attribute')
|
|
162
239
|
emit_getter('by_type(type)', 'BY_TYPE.fetch(type)')
|
|
163
240
|
end
|
|
164
241
|
end
|
|
165
242
|
end
|
|
166
|
-
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)
|
|
167
252
|
end
|
|
168
253
|
|
|
169
254
|
emit_module('Messages') do
|
|
@@ -201,6 +286,7 @@ module Ynl
|
|
|
201
286
|
'NOTIFICATIONS',
|
|
202
287
|
"Ractor.make_shareable({#{notification_operations.map { |oper| "#{oper.notification.message.value} => Notifications::#{oper.name.as_class_name}" }.join(', ') }})",
|
|
203
288
|
rbs: 'Hash[::Integer, ::Class]',
|
|
289
|
+
internal: true,
|
|
204
290
|
)
|
|
205
291
|
|
|
206
292
|
# emit request methods
|
|
@@ -218,15 +304,28 @@ module Ynl
|
|
|
218
304
|
params = header_params + attribute_params
|
|
219
305
|
params.reject! { it.type.is_a? Types::Pad }
|
|
220
306
|
|
|
221
|
-
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(', ')
|
|
222
308
|
rbs_result = request_reply.reply ? reply_class : 'void'
|
|
223
309
|
|
|
224
310
|
emit_comment(operation_doc(oper))
|
|
225
311
|
if method == 'dump'
|
|
312
|
+
method_name = "#{method.as_method_name}_#{oper.name.as_method_name}"
|
|
226
313
|
emit_rbs_comment(
|
|
227
|
-
"(#{rbs_params}) ->
|
|
314
|
+
"(#{rbs_params}) -> Array[#{rbs_result}]\n | (#{rbs_params}) { (#{rbs_result}) -> void } -> void",
|
|
228
315
|
)
|
|
229
|
-
|
|
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)")
|
|
230
329
|
indent do
|
|
231
330
|
write("exchange_message(#{method.as_symbol_literal}, #{request_class}, #{reply_class}, args, &block)")
|
|
232
331
|
end
|
|
@@ -234,6 +333,9 @@ module Ynl
|
|
|
234
333
|
emit_rbs_comment(
|
|
235
334
|
"(#{rbs_params}) -> #{rbs_result}",
|
|
236
335
|
)
|
|
336
|
+
emit_yard_options(params)
|
|
337
|
+
emit_yard_return(rbs_result)
|
|
338
|
+
emit_yard_see(request_class)
|
|
237
339
|
write("def #{method.as_method_name}_#{oper.name.as_method_name}(**args)")
|
|
238
340
|
indent do
|
|
239
341
|
write("exchange_message(#{method.as_symbol_literal}, #{request_class}, #{reply_class}, args)")
|
|
@@ -245,7 +347,7 @@ module Ynl
|
|
|
245
347
|
end
|
|
246
348
|
|
|
247
349
|
emit_class('AsyncOperations') do
|
|
248
|
-
|
|
350
|
+
emit_internal
|
|
249
351
|
write('def initialize(&exchange)')
|
|
250
352
|
indent do
|
|
251
353
|
write('@exchange = exchange')
|
|
@@ -262,12 +364,16 @@ module Ynl
|
|
|
262
364
|
attribute_params = oper.attribute_set.attributes.filter { request_reply.request.attributes.include?(it.name) }
|
|
263
365
|
attribute_params.reject! {|a| header_params.any? {|h| h.name == a.name } }
|
|
264
366
|
params = (header_params + attribute_params).reject { it.type.is_a? Types::Pad }
|
|
265
|
-
rbs_params = params.map { |it| "?#{it.name.as_variable_name}: #{it
|
|
367
|
+
rbs_params = params.map { |it| "?#{it.name.as_variable_name}: #{parameter_rbs_type(it)}" }.join(', ')
|
|
266
368
|
result = request_reply.reply ? reply_class : 'void'
|
|
267
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}>"
|
|
268
371
|
|
|
269
372
|
emit_comment(operation_doc(oper))
|
|
270
373
|
emit_rbs_comment("(#{rbs_params}) -> #{operation}")
|
|
374
|
+
emit_yard_options(params)
|
|
375
|
+
emit_yard_return(yard_operation)
|
|
376
|
+
emit_yard_see(request_class)
|
|
271
377
|
write("def #{method.as_method_name}_#{oper.name.as_method_name}(**args)")
|
|
272
378
|
indent do
|
|
273
379
|
write("@exchange.call(#{method.as_symbol_literal}, #{request_class}, #{reply_class}, args)")
|
|
@@ -279,6 +385,8 @@ module Ynl
|
|
|
279
385
|
|
|
280
386
|
emit_comment('Returns the asynchronous operation facade for this family.')
|
|
281
387
|
emit_rbs_comment('(?stream_capacity: ::Integer?) -> AsyncOperations')
|
|
388
|
+
emit_yard_param('stream_capacity', '::Integer, nil')
|
|
389
|
+
emit_yard_return('AsyncOperations')
|
|
282
390
|
write('def async(stream_capacity: nil)')
|
|
283
391
|
indent do
|
|
284
392
|
write('return @async_operations ||= build_async_facade(AsyncOperations) if stream_capacity.nil?')
|
|
@@ -329,20 +437,91 @@ module Ynl
|
|
|
329
437
|
write('end')
|
|
330
438
|
end
|
|
331
439
|
|
|
332
|
-
private def emit_const(name, value, rbs: nil)
|
|
440
|
+
private def emit_const(name, value, rbs: nil, internal: false)
|
|
441
|
+
emit_internal if internal
|
|
333
442
|
write(name, ' = ', value, *([' #: ', rbs] if rbs))
|
|
334
443
|
end
|
|
335
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
|
+
|
|
336
469
|
private def emit_getter(name, expr)
|
|
337
470
|
write('def ', name, '; ', expr, '; end')
|
|
338
471
|
end
|
|
339
472
|
|
|
340
|
-
private def
|
|
341
|
-
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:)
|
|
342
518
|
end
|
|
343
519
|
|
|
344
520
|
private def emit_comment(comment)
|
|
345
521
|
return unless comment
|
|
522
|
+
comment = comment.gsub(KERNEL_DOC_REFERENCE) do |reference|
|
|
523
|
+
"{https://docs.kernel.org/#{$~[:path]}.html #{reference}}"
|
|
524
|
+
end
|
|
346
525
|
comment.each_line(chomp: true) do |line|
|
|
347
526
|
write('# ', line)
|
|
348
527
|
end
|
|
@@ -385,7 +564,7 @@ module Ynl
|
|
|
385
564
|
|
|
386
565
|
private def emit_message_class(name, message, fixed_header:, attribute_set:, doc:)
|
|
387
566
|
emit_comment(doc)
|
|
388
|
-
emit_class(name, "
|
|
567
|
+
emit_class(name, "#@wire::Message") do
|
|
389
568
|
emit_const('TYPE', message.value)
|
|
390
569
|
emit_const('FIXED_HEADER', fixed_header&.then { 'Structs::' + it.name.as_class_name } || 'nil')
|
|
391
570
|
emit_const('ATTRIBUTE_SET', "AttributeSets::#{attribute_set.name.as_class_name}")
|
|
@@ -396,21 +575,29 @@ module Ynl
|
|
|
396
575
|
datatype = member.type
|
|
397
576
|
next if datatype.is_a? Types::Pad
|
|
398
577
|
next if attribute_params.include?(param)
|
|
399
|
-
emit_comment(
|
|
400
|
-
emit_rbs_comment('return: ' + datatype
|
|
578
|
+
emit_comment(member.doc)
|
|
579
|
+
emit_rbs_comment('return: ' + struct_member_rbs_type(datatype))
|
|
580
|
+
emit_yard_return(struct_member_yard_type(datatype))
|
|
401
581
|
emit_getter(param.as_method_name, "fixed_header.#{param.as_method_name}")
|
|
402
582
|
end
|
|
403
583
|
attribute_params.each do |param|
|
|
404
|
-
|
|
584
|
+
attribute = attribute_set.attributes.find { it.name == param }
|
|
585
|
+
datatype = attribute.type
|
|
405
586
|
next if datatype.is_a? Types::Pad
|
|
406
587
|
extending = fixed_header&.members&.any? { it.name == param }
|
|
407
588
|
|
|
408
|
-
if extending
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
|
412
600
|
end
|
|
413
|
-
emit_rbs_comment('return: ' + datatype.rbs_type)
|
|
414
601
|
if extending
|
|
415
602
|
emit_getter(param.as_method_name, "attributes[#{param.as_variable_name.as_symbol_literal}]&.value || fixed_header.#{param.as_method_name}")
|
|
416
603
|
else
|
|
@@ -420,6 +607,214 @@ module Ynl
|
|
|
420
607
|
end
|
|
421
608
|
end
|
|
422
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
|
+
|
|
423
818
|
private def emit_mcast_groups
|
|
424
819
|
groups = @ynl.mcast_groups.values
|
|
425
820
|
grouped = groups.group_by { it.name.as_variable_name }
|
|
@@ -431,67 +826,419 @@ module Ynl
|
|
|
431
826
|
|
|
432
827
|
entries = groups.map do |group|
|
|
433
828
|
key = group.name.as_variable_name.as_symbol_literal
|
|
434
|
-
value = "::Nl::McastGroup.new(#{group.name.as_string_literal}, #{group.value
|
|
829
|
+
value = "::Nl::McastGroup.new(#{group.name.as_string_literal}, #{mcast_group_value_literal(group.value)})"
|
|
435
830
|
"#{key} => #{value}"
|
|
436
831
|
end
|
|
437
832
|
emit_const(
|
|
438
833
|
'MCAST_GROUPS',
|
|
439
834
|
"Ractor.make_shareable({#{entries.join(', ')}})",
|
|
440
835
|
rbs: 'Hash[::Symbol, ::Nl::McastGroup]',
|
|
836
|
+
internal: true,
|
|
441
837
|
)
|
|
442
838
|
end
|
|
443
839
|
|
|
444
840
|
private def emit_subscription_methods
|
|
445
|
-
|
|
841
|
+
groups = @ynl.mcast_groups.values
|
|
446
842
|
.map { ":#{it.name.as_variable_name}" }
|
|
447
|
-
|
|
448
|
-
|
|
843
|
+
emit_rbs_comment("(*(#{groups.join(' | ')}) groups) -> self")
|
|
844
|
+
emit_yard_param('groups', "Array<#{groups.join(', ')}>")
|
|
845
|
+
emit_yard_return('self')
|
|
449
846
|
write('def subscribe(*groups) = super')
|
|
450
|
-
emit_rbs_comment("(*(#{
|
|
847
|
+
emit_rbs_comment("(*(#{groups.join(' | ')}) groups) -> self")
|
|
848
|
+
emit_yard_param('groups', "Array<#{groups.join(', ')}>")
|
|
849
|
+
emit_yard_return('self')
|
|
451
850
|
write('def unsubscribe(*groups) = super')
|
|
452
851
|
end
|
|
453
852
|
|
|
454
853
|
private def emit_rbs_comment(*args)
|
|
455
|
-
write('#--')
|
|
456
854
|
args.each do |arg|
|
|
457
855
|
emit_comment('@rbs ' + arg)
|
|
458
856
|
end
|
|
459
857
|
end
|
|
460
858
|
|
|
461
|
-
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)
|
|
462
1122
|
case type
|
|
463
1123
|
when Types::Pad
|
|
464
|
-
"
|
|
1124
|
+
"::Nl::DataTypes::Pad.new(#{type.length})"
|
|
465
1125
|
when Types::Flag
|
|
466
|
-
"
|
|
1126
|
+
"::Nl::DataTypes::Flag.new"
|
|
467
1127
|
when Types::Bitfield32
|
|
468
|
-
"
|
|
1128
|
+
"::Nl::DataTypes::Bitfield32.new"
|
|
469
1129
|
when Types::Scalar
|
|
470
|
-
"
|
|
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
|
|
471
1136
|
when Types::String
|
|
472
|
-
"
|
|
1137
|
+
"::Nl::DataTypes::String.new(check: #{to_checks(checks)})"
|
|
473
1138
|
when Types::Binary
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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)})"
|
|
479
1146
|
when Types::NestedAttributes
|
|
480
|
-
|
|
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})"
|
|
481
1149
|
when Types::NestTypeValue
|
|
482
|
-
|
|
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})"
|
|
483
1152
|
when Types::IndexedArray
|
|
484
|
-
"
|
|
1153
|
+
"::Nl::DataTypes::IndexedArray.new(#{to_datatype(type.sub_type, nil, owner:)})"
|
|
485
1154
|
when Types::SubMessage
|
|
486
|
-
|
|
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(', ')}})"
|
|
487
1164
|
else
|
|
488
1165
|
raise "Unknown type: #{type.class}"
|
|
489
1166
|
end
|
|
490
1167
|
end
|
|
491
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
|
+
|
|
492
1179
|
private def to_checks(checks)
|
|
493
1180
|
return 'nil' if !checks || checks.empty?
|
|
494
|
-
%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
|
|
495
1242
|
end
|
|
496
1243
|
end
|
|
497
1244
|
end
|