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.
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
- private_constant :OPERATION_FLAG_DOCS
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
- def generate(superclass: '::Nl::Family', namespace: nil)
53
- @protocol = '::Nl::Protocols::' + (@ynl.protocol == 'netlink-raw' ? 'Raw' : 'Genl')
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
- if @ynl.protocol == 'netlink-raw'
62
- emit_const('PROTOCOL', "Ractor.make_shareable(::Nl::Protocols::Raw.new(#{@ynl.name.as_string_literal}, #{@ynl.protonum}))")
63
- else
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.rbs_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
- emit_nodoc
81
- emit_const(
82
- 'MEMBERS',
83
- "Ractor.make_shareable({#{struct.members.map { "#{it.name.as_variable_name}: #{to_datatype(it.type, nil)}" }.join(', ') }})",
84
- rbs: 'Hash[::Symbol, ::Nl::_DataType]',
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, @protocol + '::AttributeSet') do
154
+ emit_class(name.as_class_name, '::Nl::AttributeSet') do
117
155
  emit_comment("Abstract class")
118
- emit_class('Attribute', @protocol + '::AttributeSet::Attribute') do
156
+ emit_class('Attribute', '::Nl::AttributeSet::Attribute') do
119
157
  end
120
158
  attr_set.attributes.each do |attr|
121
- emit_comment(attr.doc)
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.type.is_a?(Types::NestedAttributes) ||
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
- emit_nodoc
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 { emit_const(*it) }
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.type.rbs_type}" }.join(', ')
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}) -> Enumerable[#{rbs_result}]\n | (#{rbs_params}) { (#{rbs_result}) -> void } -> void",
314
+ "(#{rbs_params}) -> Array[#{rbs_result}]\n | (#{rbs_params}) { (#{rbs_result}) -> void } -> void",
228
315
  )
229
- write("def #{method.as_method_name}_#{oper.name.as_method_name}(**args, &block)")
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
- emit_nodoc
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.type.rbs_type}" }.join(', ')
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 emit_nodoc
341
- write('# :nodoc:')
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, "#{@protocol}::Message") do
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("Gets the value of `#{param}` field in the message's fixed header.")
400
- emit_rbs_comment('return: ' + datatype.rbs_type)
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
- datatype = attribute_set.attributes.find { it.name == param }.type
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
- emit_comment("Gets the value of `#{param}` attribute or fixed header in the message.")
410
- else
411
- emit_comment("Gets the value of `#{param}` attribute in the message.")
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.inspect})"
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
- group_type = @ynl.mcast_groups.values
841
+ groups = @ynl.mcast_groups.values
446
842
  .map { ":#{it.name.as_variable_name}" }
447
- .join(' | ')
448
- emit_rbs_comment("(*(#{group_type}) groups) -> self")
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("(*(#{group_type}) groups) -> self")
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 to_datatype(type, checks)
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
- "#{@protocol}::DataTypes::Pad.new(#{type.length})"
1124
+ "::Nl::DataTypes::Pad.new(#{type.length})"
465
1125
  when Types::Flag
466
- "#{@protocol}::DataTypes::Flag.new"
1126
+ "::Nl::DataTypes::Flag.new"
467
1127
  when Types::Bitfield32
468
- "#{@protocol}::DataTypes::Bitfield32.new"
1128
+ "::Nl::DataTypes::Bitfield32.new"
469
1129
  when Types::Scalar
470
- "#{@protocol}::DataTypes::Scalar.new(::Nl::Endian::#{type.byte_order.name.as_class_name}::#{type.type.as_const_name}, check: #{to_checks(checks)})"
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
- "#{@protocol}::DataTypes::String.new(check: #{to_checks(checks)})"
1137
+ "::Nl::DataTypes::String.new(check: #{to_checks(checks)})"
473
1138
  when Types::Binary
474
- # if type.struct
475
- # "Structs::" + type.struct.name.as_class_name
476
- # else
477
- "#{@protocol}::DataTypes::Binary.new(check: #{to_checks(checks)})"
478
- # end
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
- "#{@protocol}::DataTypes::NestedAttributes.new(#{type.attribute_set.name.as_class_name})"
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
- "#{@protocol}::DataTypes::NestTypeValue.new(#{type.attribute_set.name.as_class_name}, #{type.type_values.length})"
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
- "#{@protocol}::DataTypes::IndexedArray.new(#{to_datatype(type.sub_type, nil)})"
1153
+ "::Nl::DataTypes::IndexedArray.new(#{to_datatype(type.sub_type, nil, owner:)})"
485
1154
  when Types::SubMessage
486
- "#{@protocol}::DataTypes::Binary.new(check: nil)"
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