ynl 0.2.4 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/Rakefile +7 -0
- data/lib/ynl/family.rb +15 -2
- data/lib/ynl/generator.rb +978 -88
- data/lib/ynl/models.rb +49 -12
- data/lib/ynl/parser.rb +140 -49
- data/lib/ynl/version.rb +1 -1
- data/lib/ynl.rb +46 -5
- data/sig/generated/ynl/family.rbs +24 -0
- data/sig/generated/ynl/generator.rbs +163 -0
- data/sig/generated/ynl/models.rbs +165 -0
- data/sig/generated/ynl/parser.rbs +62 -0
- data/sig/generated/ynl/version.rbs +5 -0
- data/sig/generated/ynl.rbs +119 -0
- metadata +14 -7
data/lib/ynl/models.rb
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
module Ynl
|
|
2
2
|
module Models
|
|
3
|
-
Family = ::Struct.new(:name, :protocol, :protonum, :doc,
|
|
3
|
+
Family = ::Struct.new(:name, :protocol, :version, :protonum, :doc,
|
|
4
4
|
:consts, :enums, :flags, :structs,
|
|
5
5
|
:attribute_sets, :sub_messages, :operations, :mcast_groups)
|
|
6
6
|
class Family
|
|
7
7
|
def resolve
|
|
8
8
|
structs.transform_values! {|s| s.resolve(self) }
|
|
9
|
+
sub_messages.transform_values! {|s| s.resolve(self) }
|
|
9
10
|
attribute_sets.transform_values! {|s| s.resolve(self) }
|
|
11
|
+
operations.transform_values! {|operation| operation.resolve(self) }
|
|
10
12
|
self
|
|
11
13
|
end
|
|
12
14
|
|
|
@@ -28,9 +30,16 @@ module Ynl
|
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
Const = ::Struct.new(:name, :value, :doc)
|
|
33
|
+
Check = ::Struct.new(:operation, :value)
|
|
31
34
|
|
|
32
35
|
class SubMessage
|
|
33
|
-
Format = ::Struct.new(:value, :attribute_set)
|
|
36
|
+
Format = ::Struct.new(:value, :fixed_header, :attribute_set) do
|
|
37
|
+
def resolve(f)
|
|
38
|
+
self.fixed_header = fixed_header.resolve(f) if fixed_header.is_a?(Thunk)
|
|
39
|
+
self.attribute_set = attribute_set.resolve(f) if attribute_set.is_a?(Thunk)
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
end
|
|
34
43
|
|
|
35
44
|
attr_reader :name, :formats
|
|
36
45
|
|
|
@@ -40,6 +49,7 @@ module Ynl
|
|
|
40
49
|
end
|
|
41
50
|
|
|
42
51
|
def resolve(f)
|
|
52
|
+
formats.map! { it.resolve(f) }
|
|
43
53
|
self
|
|
44
54
|
end
|
|
45
55
|
end
|
|
@@ -99,11 +109,16 @@ module Ynl
|
|
|
99
109
|
end
|
|
100
110
|
|
|
101
111
|
class AttributeSet
|
|
102
|
-
Attribute = ::Struct.new(:name, :type, :value, :checks, :doc) do
|
|
112
|
+
Attribute = ::Struct.new(:name, :type, :value, :checks, :doc, :multi_attr, :enum, :enum_as_flags) do
|
|
103
113
|
def resolve(f)
|
|
104
114
|
self.type = type.resolve(f)
|
|
115
|
+
self.enum = enum.resolve(f) if enum
|
|
105
116
|
self
|
|
106
117
|
end
|
|
118
|
+
|
|
119
|
+
def multi?
|
|
120
|
+
multi_attr
|
|
121
|
+
end
|
|
107
122
|
end
|
|
108
123
|
|
|
109
124
|
attr_reader :name, :name_prefix, :attributes, :doc
|
|
@@ -134,36 +149,58 @@ module Ynl
|
|
|
134
149
|
end
|
|
135
150
|
|
|
136
151
|
def attributes
|
|
137
|
-
|
|
138
|
-
# entries in subset-of attribute sets. Deduplicate by name here until the
|
|
139
|
-
# upstream specs are fixed.
|
|
140
|
-
@attributes.uniq {|attr| attr.fetch('name') }.map do |attr|
|
|
141
|
-
name = attr.fetch('name')
|
|
142
|
-
superset.attributes.find { it.name == name } or raise ParseError "No attribute with name #{name}"
|
|
143
|
-
end
|
|
152
|
+
@attributes
|
|
144
153
|
end
|
|
145
154
|
|
|
146
155
|
def resolve(f)
|
|
156
|
+
attributes.map! { it.resolve(f) }
|
|
147
157
|
self
|
|
158
|
+
rescue
|
|
159
|
+
raise ParseError, "Failed to resolve attribute subset: #{name}"
|
|
148
160
|
end
|
|
149
161
|
end
|
|
150
162
|
|
|
163
|
+
McastGroup = ::Struct.new(:name, :value, :doc)
|
|
164
|
+
|
|
151
165
|
class Operation
|
|
152
|
-
attr_reader :name, :doc, :fixed_header, :attribute_set, :doit, :dumpit
|
|
166
|
+
attr_reader :name, :doc, :flags, :fixed_header, :attribute_set, :doit, :dumpit, :notification
|
|
153
167
|
|
|
154
|
-
def initialize(name:, doc:, fixed_header:, attribute_set:, doit:, dumpit:)
|
|
168
|
+
def initialize(name:, doc:, flags:, fixed_header:, attribute_set:, doit:, dumpit:, notification:)
|
|
155
169
|
@name = name
|
|
156
170
|
@doc = doc
|
|
171
|
+
@flags = flags
|
|
157
172
|
@fixed_header = fixed_header
|
|
158
173
|
@attribute_set = attribute_set
|
|
159
174
|
@doit = doit
|
|
160
175
|
@dumpit = dumpit
|
|
176
|
+
@notification = notification
|
|
161
177
|
end
|
|
162
178
|
|
|
163
179
|
def resolve(f)
|
|
164
180
|
@doit = @doit&.resolve(f)
|
|
165
181
|
@dumpit = @dumpit&.resolve(f)
|
|
182
|
+
@notification = @notification&.resolve(f)
|
|
183
|
+
self
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
class Notification
|
|
188
|
+
attr_reader :kind, :message, :source, :group
|
|
189
|
+
|
|
190
|
+
def initialize(kind:, message:, source:, group:)
|
|
191
|
+
@kind = kind
|
|
192
|
+
@message = message
|
|
193
|
+
@source = source
|
|
194
|
+
@group = group
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def resolve(f)
|
|
198
|
+
@message = @message.resolve(f)
|
|
199
|
+
@source = f.operations.fetch(@source) if @source
|
|
200
|
+
@group = f.mcast_groups.fetch(@group) if @group
|
|
166
201
|
self
|
|
202
|
+
rescue KeyError => error
|
|
203
|
+
raise ParseError, "Failed to resolve #{kind} notification: #{error.message}"
|
|
167
204
|
end
|
|
168
205
|
end
|
|
169
206
|
|
data/lib/ynl/parser.rb
CHANGED
|
@@ -4,6 +4,8 @@ require_relative 'models'
|
|
|
4
4
|
|
|
5
5
|
module Ynl
|
|
6
6
|
class Parser
|
|
7
|
+
# @param [String, #read] source
|
|
8
|
+
# @rbs (String | _Readable source) -> void
|
|
7
9
|
def initialize(source)
|
|
8
10
|
@yaml = YAML.load(source, aliases: true)
|
|
9
11
|
|
|
@@ -13,17 +15,24 @@ module Ynl
|
|
|
13
15
|
@structs = {}
|
|
14
16
|
|
|
15
17
|
@attribute_sets = {}
|
|
18
|
+
@attribute_definitions = {}
|
|
16
19
|
@sub_messages = {}
|
|
17
20
|
@operations = {}
|
|
18
21
|
@mcast_groups = {}
|
|
19
22
|
end
|
|
20
23
|
|
|
24
|
+
# @param [String, #to_path] path
|
|
25
|
+
# @return [Models::Family]
|
|
26
|
+
# @rbs (String | _PathLike path) -> Models::Family
|
|
21
27
|
def self.parse_file(path)
|
|
22
28
|
File.open(path) {|f| new(f) }.parse
|
|
23
29
|
end
|
|
24
30
|
|
|
31
|
+
# @return [Models::Family]
|
|
32
|
+
# @rbs () -> Models::Family
|
|
25
33
|
def parse
|
|
26
34
|
protocol = @yaml['protocol'] || 'genetlink'
|
|
35
|
+
version = @yaml.fetch('version', 1)
|
|
27
36
|
protonum = @yaml['protonum']
|
|
28
37
|
name = @yaml['name']
|
|
29
38
|
doc = translate_doc(@yaml['doc'])
|
|
@@ -79,13 +88,14 @@ module Ynl
|
|
|
79
88
|
end
|
|
80
89
|
end
|
|
81
90
|
end
|
|
82
|
-
@yaml
|
|
91
|
+
@yaml.dig('mcast-groups', 'list')&.each do |d|
|
|
83
92
|
parse_mcast_group(d)
|
|
84
93
|
end
|
|
85
94
|
|
|
86
95
|
Models::Family.new(
|
|
87
96
|
name:,
|
|
88
97
|
protocol:,
|
|
98
|
+
version:,
|
|
89
99
|
protonum:,
|
|
90
100
|
doc:,
|
|
91
101
|
consts: @consts,
|
|
@@ -106,9 +116,11 @@ module Ynl
|
|
|
106
116
|
@consts[v.name] = v
|
|
107
117
|
when 'enum'
|
|
108
118
|
v = parse_enum_flags(d, type: :enum)
|
|
119
|
+
reject_enum_flags_name_collision(v.name, @flags)
|
|
109
120
|
@enums[v.name] = v
|
|
110
121
|
when 'flags'
|
|
111
122
|
v = parse_enum_flags(d, type: :flags)
|
|
123
|
+
reject_enum_flags_name_collision(v.name, @enums)
|
|
112
124
|
@flags[v.name] = v
|
|
113
125
|
when 'struct'
|
|
114
126
|
v = parse_struct(d)
|
|
@@ -118,19 +130,33 @@ module Ynl
|
|
|
118
130
|
end
|
|
119
131
|
end
|
|
120
132
|
|
|
133
|
+
private def reject_enum_flags_name_collision(name, other_definitions)
|
|
134
|
+
return unless other_definitions.key?(name)
|
|
135
|
+
|
|
136
|
+
raise ParseError, "Definition #{name.inspect} cannot be both an enum and flags"
|
|
137
|
+
end
|
|
138
|
+
|
|
121
139
|
private def parse_const(d)
|
|
122
140
|
name = "#{d['name-prefix']}#{d.fetch('name')}"
|
|
123
|
-
|
|
141
|
+
value = d.fetch('value')
|
|
142
|
+
unless value.is_a?(Integer) || value.is_a?(String)
|
|
143
|
+
raise ParseError, "Constant value must be a string or an integer: #{name}"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
Models::Const.new(name, value, translate_doc(d['doc']))
|
|
124
147
|
end
|
|
125
148
|
|
|
126
149
|
private def parse_enum_flags(d, type:)
|
|
127
150
|
cls = type == :enum ? Models::Enum : Models::Flags
|
|
128
151
|
result = cls.new(name: d.fetch('name'), doc: translate_doc(d['doc']))
|
|
129
152
|
|
|
130
|
-
start_value = d['start
|
|
131
|
-
|
|
153
|
+
start_value = d['value-start'] || 0
|
|
154
|
+
raw_value = start_value
|
|
132
155
|
|
|
133
156
|
d.fetch('entries').each do |v|
|
|
157
|
+
explicit_value = v.is_a?(Hash) ? v['value'] : nil
|
|
158
|
+
raw_value = explicit_value unless explicit_value.nil?
|
|
159
|
+
value = type == :enum ? raw_value : 1 << raw_value
|
|
134
160
|
case v
|
|
135
161
|
when String
|
|
136
162
|
entry = cls::Entry.new(name: v, value:)
|
|
@@ -142,7 +168,7 @@ module Ynl
|
|
|
142
168
|
|
|
143
169
|
result.entries << entry
|
|
144
170
|
|
|
145
|
-
|
|
171
|
+
raw_value += 1
|
|
146
172
|
|
|
147
173
|
rescue
|
|
148
174
|
raise ParseError, "Failed to parse enum/flags entry: #{v.fetch('name')}"
|
|
@@ -164,6 +190,7 @@ module Ynl
|
|
|
164
190
|
when 'binary'
|
|
165
191
|
Types::Binary.new(
|
|
166
192
|
struct: d['struct'] ? Models::Thunk.new {|f| f.structs.fetch(d['struct']) } : nil,
|
|
193
|
+
length: d['len'],
|
|
167
194
|
display_hint: d['display-hint'],
|
|
168
195
|
)
|
|
169
196
|
when 'pad'
|
|
@@ -184,10 +211,15 @@ module Ynl
|
|
|
184
211
|
byte_order: parse_byte_order(d['byte-order']),
|
|
185
212
|
)
|
|
186
213
|
when 'binary'
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
214
|
+
if d.key?('sub-type')
|
|
215
|
+
Types::PackedArray.new(sub_type: parse_packed_array_sub_type(d))
|
|
216
|
+
else
|
|
217
|
+
Types::Binary.new(
|
|
218
|
+
struct: d['struct'] ? Models::Thunk.new {|f| f.structs.fetch(d.fetch('struct')) } : nil,
|
|
219
|
+
length: nil,
|
|
220
|
+
display_hint: d['display-hint'],
|
|
221
|
+
)
|
|
222
|
+
end
|
|
191
223
|
when 'string'
|
|
192
224
|
Types::String.new
|
|
193
225
|
when 'nest'
|
|
@@ -200,11 +232,12 @@ module Ynl
|
|
|
200
232
|
)
|
|
201
233
|
when 'nest-type-value'
|
|
202
234
|
if d['nested-attributes']
|
|
203
|
-
Types::
|
|
235
|
+
Types::NestTypeValue.new(
|
|
204
236
|
attribute_set: Models::Thunk.new {|f| f.attribute_sets.fetch(d.fetch('nested-attributes')) },
|
|
237
|
+
type_values: d.fetch('type-value'),
|
|
205
238
|
)
|
|
206
239
|
else
|
|
207
|
-
Types::Binary.new(struct: nil, display_hint: nil)
|
|
240
|
+
Types::Binary.new(struct: nil, length: nil, display_hint: nil)
|
|
208
241
|
end
|
|
209
242
|
when 'sub-message'
|
|
210
243
|
Types::SubMessage.new(
|
|
@@ -237,6 +270,7 @@ module Ynl
|
|
|
237
270
|
when 'binary'
|
|
238
271
|
Types::Binary.new(
|
|
239
272
|
struct: nil,
|
|
273
|
+
length: nil,
|
|
240
274
|
display_hint: d['display-hint'],
|
|
241
275
|
)
|
|
242
276
|
when 'nest'
|
|
@@ -248,13 +282,26 @@ module Ynl
|
|
|
248
282
|
end
|
|
249
283
|
end
|
|
250
284
|
|
|
285
|
+
private def parse_packed_array_sub_type(d)
|
|
286
|
+
sub_type = d.fetch('sub-type')
|
|
287
|
+
case sub_type
|
|
288
|
+
when 'u8', 'u16', 'u32', 'u64', 's8', 's16', 's32', 's64', 'int'
|
|
289
|
+
Types::Scalar.new(
|
|
290
|
+
type: sub_type,
|
|
291
|
+
byte_order: parse_byte_order(d['byte-order']),
|
|
292
|
+
)
|
|
293
|
+
else
|
|
294
|
+
raise ParseError, "Unknown packed-array sub-type: #{sub_type}"
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
251
298
|
private def parse_byte_order(v)
|
|
252
299
|
case v
|
|
253
300
|
when nil
|
|
254
301
|
:host
|
|
255
302
|
when 'big-endian'
|
|
256
303
|
:big
|
|
257
|
-
when '
|
|
304
|
+
when 'little-endian'
|
|
258
305
|
:little
|
|
259
306
|
else
|
|
260
307
|
raise ParseError, "Unknown endian: #{v}"
|
|
@@ -287,21 +334,8 @@ module Ynl
|
|
|
287
334
|
|
|
288
335
|
private def parse_check(op, value_literal)
|
|
289
336
|
case op
|
|
290
|
-
when 'max'
|
|
291
|
-
value
|
|
292
|
-
return %{raise ArgumentError, "Value \#{it.inspect} is greater than maximum #{value}" unless it <= #{value}}
|
|
293
|
-
when 'min'
|
|
294
|
-
value = parse_value(value_literal)
|
|
295
|
-
return %{raise ArgumentError, "Value \#{it.inspect} is less than minimum #{value}" unless it >= #{value}}
|
|
296
|
-
when 'min-len'
|
|
297
|
-
value = parse_value(value_literal)
|
|
298
|
-
return %{raise ArgumentError, "Value \#{it.inspect} is shorter than minimum length #{value}" unless it.bytesize >= #{value}}
|
|
299
|
-
when 'max-len'
|
|
300
|
-
value = parse_value(value_literal)
|
|
301
|
-
return %{raise ArgumentError, "Value \#{it.inspect} is longer than maximum length #{value}" unless it.bytesize <= #{value}}
|
|
302
|
-
when 'exact-len'
|
|
303
|
-
value = parse_value(value_literal)
|
|
304
|
-
return %{raise ArgumentError, "Value \#{it.inspect} is not equal to length #{value}" unless it.bytesize == #{value}}
|
|
337
|
+
when 'max', 'min', 'min-len', 'max-len', 'exact-len'
|
|
338
|
+
Models::Check.new(operation: op, value: parse_value(value_literal))
|
|
305
339
|
when 'unterminated-ok'
|
|
306
340
|
return nil
|
|
307
341
|
else
|
|
@@ -321,7 +355,7 @@ module Ynl
|
|
|
321
355
|
(2 ** 31) - 1
|
|
322
356
|
when String
|
|
323
357
|
const = @consts[v] or raise ParseError, "Unknown value: #{v}"
|
|
324
|
-
const
|
|
358
|
+
const
|
|
325
359
|
else
|
|
326
360
|
raise ParseError, "Unknown value: #{v}"
|
|
327
361
|
end
|
|
@@ -331,38 +365,72 @@ module Ynl
|
|
|
331
365
|
name = d.fetch('name')
|
|
332
366
|
if subset_of = d['subset-of']
|
|
333
367
|
superset = @attribute_sets.fetch(subset_of)
|
|
334
|
-
|
|
368
|
+
inherited = @attribute_definitions.fetch(subset_of).to_h { [it.fetch('name'), it] }
|
|
369
|
+
definitions = d.fetch('attributes').uniq { it.fetch('name') }.map do |override|
|
|
370
|
+
parent = inherited.fetch(override.fetch('name'))
|
|
371
|
+
parent.merge(override).merge('value' => parent.fetch('value'))
|
|
372
|
+
end
|
|
373
|
+
attributes = definitions.filter_map { parse_attribute(it) }
|
|
374
|
+
result = Models::AttributeSubset.new(name:, superset:, attributes:, doc: translate_doc(d['doc']))
|
|
335
375
|
else
|
|
336
376
|
name_prefix = d['name_prefix']
|
|
337
377
|
result = Models::AttributeSet.new(name:, name_prefix:, doc: translate_doc(d['doc']))
|
|
338
378
|
value = 0
|
|
339
|
-
|
|
340
|
-
d.fetch('attributes').each do |v|
|
|
379
|
+
definitions = d.fetch('attributes').map do |v|
|
|
341
380
|
value = v.fetch('value', value + 1)
|
|
342
|
-
|
|
343
|
-
attribute = Models::AttributeSet::Attribute.new(name: v.fetch('name'), type: type, value:)
|
|
344
|
-
result.attributes << attribute
|
|
345
|
-
if checks = v['checks']
|
|
346
|
-
attribute.checks = parse_checks(checks)
|
|
347
|
-
end
|
|
348
|
-
end
|
|
349
|
-
rescue
|
|
350
|
-
raise ParseError, "Failed to parse attribute: #{v.fetch('name')}"
|
|
381
|
+
v.merge('value' => value)
|
|
351
382
|
end
|
|
383
|
+
result.attributes.concat(definitions.filter_map { parse_attribute(it) })
|
|
352
384
|
end
|
|
353
385
|
|
|
354
386
|
@attribute_sets[name] = result
|
|
387
|
+
@attribute_definitions[name] = definitions
|
|
355
388
|
|
|
356
|
-
rescue
|
|
357
|
-
raise ParseError, "Failed to parse attribute set
|
|
389
|
+
rescue => error
|
|
390
|
+
raise ParseError, "Failed to parse attribute set #{d.fetch('name')}: #{error.message}"
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
private def parse_attribute(d)
|
|
394
|
+
multi_attr = d.fetch('multi-attr', false)
|
|
395
|
+
unless multi_attr == true || multi_attr == false
|
|
396
|
+
raise ParseError, "multi-attr must be a boolean"
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
enum_as_flags = d.fetch('enum-as-flags', false)
|
|
400
|
+
unless enum_as_flags == true || enum_as_flags == false
|
|
401
|
+
raise ParseError, "enum-as-flags must be a boolean"
|
|
402
|
+
end
|
|
403
|
+
if enum_as_flags && !d['enum']
|
|
404
|
+
raise ParseError, "enum-as-flags requires enum"
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
return unless type = parse_attribute_type(d)
|
|
408
|
+
|
|
409
|
+
Models::AttributeSet::Attribute.new(
|
|
410
|
+
name: d.fetch('name'),
|
|
411
|
+
type:,
|
|
412
|
+
value: d.fetch('value'),
|
|
413
|
+
checks: (parse_checks(d['checks']) if d['checks']),
|
|
414
|
+
doc: translate_doc(d['doc']),
|
|
415
|
+
multi_attr:,
|
|
416
|
+
enum: d['enum']&.then { |ref| Models::Thunk.new {|f| f.find_type(ref) } },
|
|
417
|
+
enum_as_flags:,
|
|
418
|
+
)
|
|
419
|
+
rescue => error
|
|
420
|
+
raise ParseError, "Failed to parse attribute #{d.fetch('name')}: #{error.message}"
|
|
358
421
|
end
|
|
359
422
|
|
|
360
423
|
private def parse_sub_message(d)
|
|
361
424
|
name = d.fetch('name')
|
|
362
425
|
result = Models::SubMessage.new(name:)
|
|
363
426
|
d.fetch('formats', []).each do |fmt|
|
|
364
|
-
|
|
365
|
-
|
|
427
|
+
fixed_header = fmt['fixed-header']&.then do |ref|
|
|
428
|
+
Models::Thunk.new {|f| f.structs.fetch(ref) }
|
|
429
|
+
end
|
|
430
|
+
attr_set = fmt['attribute-set']&.then do |ref|
|
|
431
|
+
Models::Thunk.new {|f| f.attribute_sets.fetch(ref) }
|
|
432
|
+
end
|
|
433
|
+
result.formats << Models::SubMessage::Format.new(fmt.fetch('value'), fixed_header, attr_set)
|
|
366
434
|
end
|
|
367
435
|
@sub_messages[name] = result
|
|
368
436
|
end
|
|
@@ -384,18 +452,35 @@ module Ynl
|
|
|
384
452
|
|
|
385
453
|
doit = d['do']&.then { parse_request_reply(it, default_request_id: request_id, default_reply_id: reply_id) }
|
|
386
454
|
dumpit = d['dump']&.then { parse_request_reply(it, default_request_id: request_id, default_reply_id: reply_id) }
|
|
387
|
-
|
|
388
|
-
# TODO: notify
|
|
455
|
+
notification = parse_notification(d, default_id: reply_id)
|
|
389
456
|
|
|
390
457
|
@operations[name] = Models::Operation.new(
|
|
391
|
-
name:, doc: translate_doc(d['doc']), fixed_header:, attribute_set:,
|
|
392
|
-
doit:, dumpit:,
|
|
458
|
+
name:, doc: translate_doc(d['doc']), flags: d.fetch('flags', []), fixed_header:, attribute_set:,
|
|
459
|
+
doit:, dumpit:, notification:,
|
|
393
460
|
)
|
|
394
461
|
end
|
|
395
462
|
|
|
463
|
+
private def parse_notification(d, default_id:)
|
|
464
|
+
if source = d['notify']
|
|
465
|
+
Models::Notification.new(
|
|
466
|
+
kind: :notify,
|
|
467
|
+
message: parse_message({}, default_id:),
|
|
468
|
+
source:,
|
|
469
|
+
group: d['mcgrp'],
|
|
470
|
+
)
|
|
471
|
+
elsif event = d['event']
|
|
472
|
+
Models::Notification.new(
|
|
473
|
+
kind: :event,
|
|
474
|
+
message: parse_message(event, default_id:),
|
|
475
|
+
source: nil,
|
|
476
|
+
group: d['mcgrp'],
|
|
477
|
+
)
|
|
478
|
+
end
|
|
479
|
+
end
|
|
480
|
+
|
|
396
481
|
private def parse_request_reply(d, default_request_id:, default_reply_id:)
|
|
397
482
|
Models::RequestReply.new(
|
|
398
|
-
request: d
|
|
483
|
+
request: parse_message(d.fetch('request', {}), default_id: default_request_id),
|
|
399
484
|
reply: d['reply']&.then { parse_message(it, default_id: default_reply_id) },
|
|
400
485
|
)
|
|
401
486
|
end
|
|
@@ -405,6 +490,12 @@ module Ynl
|
|
|
405
490
|
end
|
|
406
491
|
|
|
407
492
|
private def parse_mcast_group(d)
|
|
493
|
+
name = d.fetch('name')
|
|
494
|
+
@mcast_groups[name] = Models::McastGroup.new(
|
|
495
|
+
name,
|
|
496
|
+
d['value'],
|
|
497
|
+
translate_doc(d['doc']),
|
|
498
|
+
)
|
|
408
499
|
end
|
|
409
500
|
|
|
410
501
|
private def translate_doc(doc)
|
data/lib/ynl/version.rb
CHANGED
data/lib/ynl.rb
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Parser for YNL netlink specification
|
|
2
2
|
#
|
|
3
3
|
# See: https://www.kernel.org/doc/html/latest/userspace-api/netlink/specs.html
|
|
4
4
|
|
|
5
5
|
require_relative 'ynl/family'
|
|
6
6
|
|
|
7
|
+
# Ynl parser and code generator. See {Family} for library entrypoint.
|
|
7
8
|
module Ynl
|
|
9
|
+
# @rbs!
|
|
10
|
+
# interface _Readable
|
|
11
|
+
# def read: () -> String
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# interface _Writable
|
|
15
|
+
# def write: (*String strings) -> untyped
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# interface _PathLike
|
|
19
|
+
# def to_path: () -> String
|
|
20
|
+
# end
|
|
21
|
+
|
|
8
22
|
class ParseError < StandardError; end
|
|
9
23
|
|
|
24
|
+
# @private
|
|
10
25
|
module Types
|
|
11
26
|
Scalar = Data.define(:type, :byte_order) do
|
|
12
27
|
def resolve(f)
|
|
@@ -27,13 +42,25 @@ module Ynl
|
|
|
27
42
|
end
|
|
28
43
|
end
|
|
29
44
|
Binary = Struct.new(:struct, :length, :display_hint) do
|
|
45
|
+
using Generator::Refinements
|
|
46
|
+
|
|
30
47
|
def resolve(f)
|
|
31
48
|
self.struct = struct.resolve(f) if self.struct
|
|
32
49
|
self
|
|
33
50
|
end
|
|
34
51
|
|
|
35
52
|
def rbs_type
|
|
36
|
-
'
|
|
53
|
+
struct ? 'Structs::' + struct.name.as_class_name : '::String'
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
PackedArray = Struct.new(:sub_type) do
|
|
57
|
+
def resolve(f)
|
|
58
|
+
self.sub_type = sub_type.resolve(f)
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def rbs_type
|
|
63
|
+
"::Array[#{sub_type.rbs_type}]"
|
|
37
64
|
end
|
|
38
65
|
end
|
|
39
66
|
NestedAttributes = Struct.new(:attribute_set) do
|
|
@@ -48,6 +75,20 @@ module Ynl
|
|
|
48
75
|
'AttributeSets::' + attribute_set.name.as_class_name
|
|
49
76
|
end
|
|
50
77
|
end
|
|
78
|
+
NestTypeValue = Struct.new(:attribute_set, :type_values) do
|
|
79
|
+
using Generator::Refinements
|
|
80
|
+
|
|
81
|
+
def resolve(f)
|
|
82
|
+
self.attribute_set = attribute_set.resolve(f)
|
|
83
|
+
self
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def rbs_type
|
|
87
|
+
type = 'AttributeSets::' + attribute_set.name.as_class_name
|
|
88
|
+
type_values.length.times { type = "::Hash[::Integer, #{type}]" }
|
|
89
|
+
type
|
|
90
|
+
end
|
|
91
|
+
end
|
|
51
92
|
SubMessage = Struct.new(:sub_message, :selector) do
|
|
52
93
|
def resolve(f)
|
|
53
94
|
self.sub_message = sub_message.resolve(f)
|
|
@@ -55,7 +96,7 @@ module Ynl
|
|
|
55
96
|
end
|
|
56
97
|
|
|
57
98
|
def rbs_type
|
|
58
|
-
'
|
|
99
|
+
'::Nl::SubMessage'
|
|
59
100
|
end
|
|
60
101
|
end
|
|
61
102
|
|
|
@@ -85,7 +126,7 @@ module Ynl
|
|
|
85
126
|
end
|
|
86
127
|
|
|
87
128
|
def rbs_type
|
|
88
|
-
'::
|
|
129
|
+
'::Nl::Bitfield32'
|
|
89
130
|
end
|
|
90
131
|
end
|
|
91
132
|
|
|
@@ -96,7 +137,7 @@ module Ynl
|
|
|
96
137
|
end
|
|
97
138
|
|
|
98
139
|
def rbs_type
|
|
99
|
-
|
|
140
|
+
"::Array[#{sub_type.rbs_type}]"
|
|
100
141
|
end
|
|
101
142
|
end
|
|
102
143
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Generated from lib/ynl/family.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Ynl
|
|
4
|
+
# Entry points for generating family classes from YNL specifications.
|
|
5
|
+
class Family
|
|
6
|
+
# Builds a family class from a YNL specification file.
|
|
7
|
+
#
|
|
8
|
+
# @param [String, #to_path] path a path to a YNL specification file
|
|
9
|
+
# @return [Class<Nl::Family>] the generated family class
|
|
10
|
+
# @rbs (String | _PathLike path) -> Class
|
|
11
|
+
def self.build: (String | _PathLike path) -> Class
|
|
12
|
+
|
|
13
|
+
# Generates Ruby source code to define a family class from a YNL specification.
|
|
14
|
+
#
|
|
15
|
+
# @param [String, #read] source a YAML string or readable IO containing the YNL specification
|
|
16
|
+
# @param [#write] out the destination for the generated source code
|
|
17
|
+
# @option kwargs [String, nil] superclass the name of the generated class's superclass
|
|
18
|
+
# @option kwargs [String, nil] namespace the namespace in which to define the family class
|
|
19
|
+
# @option kwargs [String, nil] default_resolver the expression used as the default family resolver
|
|
20
|
+
# @return [String] the generated family class name
|
|
21
|
+
# @rbs (String | _Readable source, _Writable out, ?superclass: String?, ?namespace: String?, ?default_resolver: String?) -> String
|
|
22
|
+
def self.generate: (String | _Readable source, _Writable out, ?superclass: String?, ?namespace: String?, ?default_resolver: String?) -> String
|
|
23
|
+
end
|
|
24
|
+
end
|