slow_blink 0.0.4 → 0.0.5

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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/ext/slow_blink/ext_schema_parser/lexer.c +168 -177
  3. data/ext/slow_blink/ext_schema_parser/lexer.h +1 -1
  4. data/ext/slow_blink/ext_schema_parser/parser.c +303 -306
  5. data/ext/slow_blink/ext_schema_parser/parser.h +11 -12
  6. data/ext/slow_blink/{ext_compact_encoder → message/ext_compact_encoder}/ext_compact_encoder.c +3 -1
  7. data/ext/slow_blink/message/ext_compact_encoder/extconf.rb +4 -0
  8. data/lib/slow_blink/error.rb +1 -1
  9. data/lib/slow_blink/message/binary.rb +19 -26
  10. data/lib/slow_blink/message/boolean.rb +17 -17
  11. data/lib/slow_blink/message/date.rb +24 -0
  12. data/lib/slow_blink/message/decimal.rb +34 -0
  13. data/lib/slow_blink/message/enumeration.rb +24 -23
  14. data/lib/slow_blink/message/field.rb +45 -6
  15. data/lib/slow_blink/message/fixed.rb +35 -25
  16. data/lib/slow_blink/message/floating_point.rb +16 -17
  17. data/lib/slow_blink/message/group.rb +242 -136
  18. data/lib/slow_blink/message/integer.rb +77 -51
  19. data/lib/slow_blink/message/model.rb +121 -85
  20. data/lib/slow_blink/message/sequence.rb +43 -28
  21. data/lib/slow_blink/message/string.rb +17 -31
  22. data/lib/slow_blink/message/time.rb +55 -17
  23. data/lib/slow_blink/message/time_of_day.rb +40 -50
  24. data/lib/slow_blink/schema.rb +17 -6
  25. data/lib/slow_blink/version.rb +1 -1
  26. data/rakefile +12 -7
  27. data/test/tc_incr_annote.rb +16 -0
  28. data/test/tc_inputs.rb +2 -2
  29. data/test/tc_model_dynamic_group.rb +105 -0
  30. data/test/tc_model_extension.rb +143 -0
  31. data/test/tc_model_static_group.rb +27 -36
  32. data/test/tc_model_think_blink.rb +18 -35
  33. metadata +12 -9
  34. data/ext/slow_blink/ext_compact_encoder/extconf.rb +0 -4
  35. /data/ext/slow_blink/{ext_compact_encoder → message/ext_compact_encoder}/compact_encoder.c +0 -0
  36. /data/ext/slow_blink/{ext_compact_encoder → message/ext_compact_encoder}/compact_encoder.h +0 -0
@@ -19,142 +19,281 @@
19
19
 
20
20
  module SlowBlink::Message
21
21
 
22
- class StaticGroup
23
-
24
- def self.implements
25
- @implements
26
- end
22
+ # A Group may form a complete message or be nested as a {DynamicGroup}
23
+ class Group
27
24
 
25
+ # @return [Hash<Field>] group fields
28
26
  def self.fields
29
27
  @fields
30
28
  end
31
29
 
32
- def self.opt?
33
- @opt
34
- end
35
-
30
+ # @return [Integer,nil] group identifier
36
31
  def self.id
37
32
  @id
38
33
  end
39
34
 
35
+ # @return [String] name of group
40
36
  def self.name
41
37
  @name
42
38
  end
43
39
 
44
- def self.from_compact!(input)
45
- if @opt
46
- if input.getPresent!
47
- fields = {}
48
- @fields.each do |f|
49
- fields[f.name] = f.from_compact!(input)
50
- end
51
- self.new(fields)
52
- else
53
- self.new(nil)
54
- end
40
+ # @param input [String] Blink compact form
41
+ # @param stack [Array] used to measure depth of recursion
42
+ # @return [Group, nil]
43
+ # @raise [Error] recursion depth limit
44
+ def self.from_compact!(input, stack)
45
+
46
+ fields = {}
47
+ extensions = []
48
+
49
+ if stack.size < @maxRecursion
50
+ stack << self
55
51
  else
56
- fields = {}
57
- @fields.each do |f|
58
- fields[f.name] = f.from_compact!(input)
59
- end
60
- self.new(fields)
61
- end
52
+ raise Error.new "stack limit"
53
+ end
54
+
55
+ @fields.each do |fn, fd|
56
+ fields[fn] = fd.from_compact!(input, stack)
57
+ end
58
+
59
+ if input.size > 0
60
+ expected = input.getU32!
61
+ while extensions.size != expected do
62
+ extensions << @extensionObject.from_compact!(input, stack)
63
+ end
64
+ end
65
+
66
+ if input.size != 0
67
+ raise Error.new "extra bytes at end of group after extensions"
68
+ end
69
+
70
+ group = self.new(fields)
71
+
72
+ extensions.each do |e|
73
+ group.extension << group
74
+ end
75
+
76
+ stack.pop
77
+
78
+ group
79
+
62
80
  end
63
81
 
82
+ # @return [Array<Group>] group extension objects
83
+ attr_reader :extension
84
+
85
+ # set a field value
86
+ #
87
+ # @param name [String] name of field
88
+ # @param value [Object]
89
+ # @raise [IndexError, TypeError]
64
90
  def []=(name, value)
65
91
  if @value[name]
66
92
  @value[name].set(value)
93
+ self
67
94
  else
68
- raise "field #{name} is not defined in this group"
69
- end
95
+ raise IndexError.new "field #{name} is not defined in this group"
96
+ end
70
97
  end
71
98
 
72
- def [](name)
99
+ # Get a field value
100
+ #
101
+ # @param name [String] name of field
102
+ # @return [Object]
103
+ # @raise [IndexError, TypeError]
104
+ def [](name)
73
105
  if @value[name]
74
- @value[name].get
106
+ @value[name].get
75
107
  else
76
- raise "field #{name} is not defined in this group"
77
- end
108
+ raise IndexError.new "field #{name} is not defined in this group"
109
+ end
78
110
  end
79
111
 
80
- def set(value)
81
- if value
82
- self.class.fields.each do |f|
83
- @value[f.name].set(value[f.name])
84
- @empty = false
85
- end
86
- elsif self.class.opt?
87
- @empty = true
112
+ # Get this group
113
+ # @return [Group] self
114
+ def get
115
+ self
116
+ end
117
+
118
+ # Get the value hash directly
119
+ # @return [Hash]
120
+ def fields
121
+ @value
122
+ end
123
+
124
+ # Set the contents of this group
125
+ #
126
+ # This method accepts one of two options:
127
+ #
128
+ # First option is a Hash of fields. Values may be literals to be set to fields objects,
129
+ # or instances of the field objects to completely replace existing field objects
130
+ #
131
+ # Second option is a {Group} where #{Group#fields} will be used in the same manner as the first option.
132
+ #
133
+ # @param value [Hash, Group] fields to set
134
+ # @raise [IndexError, TypeError]
135
+ def set(value)
136
+ if value.kind_of? Hash
137
+ value.each do |fn, fv|
138
+ if @value[fn]
139
+ # replace entire field
140
+ if fv.is_a? @value[fn].class
141
+ @value[fn] = fv
142
+ # set value of field
143
+ else
144
+ @value[fn].set(fv)
145
+ end
146
+ else
147
+ raise IndexError.new "field '#{fn}' is unknown"
148
+ end
149
+ end
150
+ # replace @value with value from another instance of self.class
151
+ elsif value.is_a? self.class
152
+ @value = value.fields.to_h
88
153
  else
89
- raise "this group cannot be NULL"
154
+ raise TypeError.new "expecting a Hash or a StaticGroup instance"
90
155
  end
91
156
  end
92
157
 
93
- def get
94
- @value
158
+ # Create a Group
159
+ #
160
+ # @param fields [Hash] associative array of slow blink objects or native values
161
+ def initialize(fields={})
162
+ @extension = []
163
+ @fields = self.class.fields
164
+ @value = {}
165
+ self.class.fields.each do |fn, fd|
166
+ @value[fn] = fd.new(nil)
167
+ end
168
+ set(fields)
169
+ end
170
+
171
+ def encode_compact
172
+ if self.class.id
173
+ to_compact("")
174
+ else
175
+ raise Error.new "cannot encode a group without an ID"
176
+ end
95
177
  end
96
178
 
97
- def initialize(fields)
98
- empty = false
99
- @value = self.class.fields.inject({}) do |value, f|
100
- value[f.name] = f.new(nil)
101
- end
102
- if fields
103
- set(fields.to_h)
179
+ def to_compact(out)
180
+ group = "".putU64(self.class.id)
181
+ @value.each do |fn, fv|
182
+ fv.to_compact(group)
104
183
  end
184
+ if @extension.size > 0
185
+ group.putU32(@extension.size)
186
+ @extension.each do |e|
187
+ #if e.is_a? @extensionObject
188
+ e.to_compact(group)
189
+ #else
190
+ # raise Error.new "cannot convert unknown extension object to compact form"
191
+ #end
192
+ end
193
+ end
194
+ out.putU32(group.size)
195
+ out << group
105
196
  end
106
197
 
107
- def fields
108
- @value
198
+ end
199
+
200
+ # A StaticGroup is a kind of {Group} that can only exist as the contents of a {Field}
201
+ class StaticGroup < Group
202
+
203
+ # @note optionality affects how instances of this type are encoded
204
+ #
205
+ # @return [true,false] is optional
206
+ def self.opt?
207
+ @opt
109
208
  end
209
+
210
+ # @param input [String] Blink compact form
211
+ # @param stack [Array] used to measure depth of recursion
212
+ # @return [Group, nil]
213
+ # @raise [Error] recursion depth limit
214
+ def self.from_compact!(input, stack)
110
215
 
111
- def to_compact(out)
112
- if @value
113
- if self.class.opt?
114
- out.putPresent
115
- self.class.fields.each do |f|
116
- @value[f.name].to_compact(out)
117
- end
118
- out
119
- else
120
- self.class.fields.each do |f|
121
- @value[f.name].to_compact(out)
122
- end
123
- out
216
+ if stack.size < @maxRecursion
217
+ stack << self
218
+ else
219
+ raise Error.new "stack limit"
220
+ end
221
+
222
+ if @opt
223
+ present = input.getPresent
224
+ else
225
+ present = true
226
+ end
227
+ if present
228
+ fields = {}
229
+ @fields.each do |fn, fd|
230
+ fields[fn] = fd.from_compact!(input, stack)
124
231
  end
232
+ result = self.new(fields)
125
233
  else
126
- out.putPresent(false)
234
+ nil
127
235
  end
236
+
237
+ stack.pop
238
+ result
239
+
128
240
  end
129
241
 
130
- def to_h
131
- @value
242
+ # @note {StaticGroup} cannot have extensions therefore this method will raise a NoMethodError
243
+ # @raise [NoMethodError]
244
+ def extension
245
+ raise NoMethodError.new "static groups cannot have extensions"
246
+ end
247
+
248
+ def to_compact(out)
249
+ if self.class.opt?
250
+ out.putPresent
251
+ end
252
+ @value.each do |fn, fv|
253
+ fv.to_compact(out)
254
+ end
255
+ out
132
256
  end
133
257
 
134
258
  end
135
259
 
260
+ # A DynamicGroup has a {Group} which has a {Group.id} that appears in {DynamicGroup.permitted} list
136
261
  class DynamicGroup
137
-
262
+
263
+ # @return [Hash] Hash of all groups referenced by name
138
264
  def self.groups
139
265
  @groups
140
266
  end
141
267
 
268
+ # @return [Hash] Hash of all tagged groups referenced by ID
269
+ def self.taggedGroups
270
+ @taggedGroups
271
+ end
272
+
273
+ # @return [Array<Integer>] Array of group IDs that can be encapsulated by this DynamicGroup
142
274
  def self.permitted
143
275
  @permitted
144
276
  end
145
277
 
146
- def self.opt?
147
- @opt
148
- end
278
+ # @param input [String] Blink compact form
279
+ # @param stack [Array] used to measure depth of recursion
280
+ # @return [Group, nil]
281
+ # @raise [Error] recursion depth limit
282
+ def self.from_compact!(input, stack)
149
283
 
150
- def self.from_compact!(input)
284
+ if stack.size < @maxRecursion
285
+ stack << self
286
+ else
287
+ raise Error.new "stack limit"
288
+ end
289
+
151
290
  buf = input.getBinary!
152
291
  if buf.size > 0
153
292
  id = buf.getU64!
154
- group = @groups[id]
293
+ group = @taggedGroups[id]
155
294
  if group
156
295
  if @permitted.include? group.id
157
- self.new(group.from_compact!(buf))
296
+ result = self.new(group.from_compact!(buf, stack))
158
297
  else
159
298
  raise Error.new "W15: Group is known but unexpected"
160
299
  end
@@ -162,84 +301,51 @@ module SlowBlink::Message
162
301
  raise Error.new "W2: Group id #{group.id} is unknown"
163
302
  end
164
303
  else
165
- if self == ModelInstance
166
- raise Error.new "W1: Top level group cannot be null"
167
- elsif @opt
168
- self.new(nil)
169
- else
170
- raise Error.new "W5: Value cannot be null"
171
- end
304
+ raise Error.new "W5: Value cannot be null"
172
305
  end
173
- end
174
306
 
175
- def []=(name, value)
176
- if @value
177
- @value[name] = value
178
- else
179
- raise "undefined dynamic group"
180
- end
181
- end
307
+ stack.pop
308
+ result
182
309
 
183
- def [](name)
184
- if @value
185
- @value[name]
186
- else
187
- raise "undefined dynamic group"
188
- end
189
310
  end
190
311
 
191
- def set(value)
192
- if value
193
- if self.class.groups.values.include? value.class
194
- if self.class.permitted.include? value.class.id
195
- @value = value
196
- else
197
- raise "group is not a permitted group"
198
- end
199
- else
200
- raise "value is not a group instance"
312
+ def set(value)
313
+ # is group one of the groups we understand?
314
+ if self.class.taggedGroups.values.detect{|g| value.is_a? g}
315
+ # is this group permitted?
316
+ if self.class.permitted.include? value.class.id
317
+ @value = value
318
+ else
319
+ raise "group valid but not expected"
201
320
  end
202
- elsif self.class.opt?
203
- @value = nil
321
+ # native values
322
+ elsif value.kind_of? Hash
323
+ @value.set(value)
204
324
  else
205
325
  raise
206
- end
326
+ end
207
327
  end
208
328
 
329
+ # @return [Group] contained group
209
330
  def get
210
- @value
331
+ @value.get
211
332
  end
212
333
 
213
- def initialize(value)
214
- if value
215
- set(value)
216
- else
217
- @value = nil
218
- end
219
- end
334
+ # Calls {Group#extension}
335
+ # @return [Array]
336
+ def extension
337
+ @value.extension
338
+ end
220
339
 
221
- def to_compact(out="")
222
- if @value
223
- group = @value.to_compact("".putU64(@value.class.id))
224
- out.putU32(group.size)
225
- out << group
226
- else
227
- out.putU32(nil)
228
- end
340
+ # @param value [Group, Hash]
341
+ def initialize(value)
342
+ set(value)
229
343
  end
230
344
 
231
- # @api user
232
- #
233
- # Encode {Group} as Blink compact form
234
- #
235
- # @return [String]
236
- #
237
- def encode_compact
238
- to_compact
239
- end
345
+ def to_compact(out)
346
+ @value.to_compact(out)
347
+ end
240
348
 
241
349
  end
242
350
 
243
-
244
-
245
351
  end
@@ -21,70 +21,66 @@ module SlowBlink::Message
21
21
 
22
22
  class INTEGER
23
23
 
24
- def self.name
25
- @name
26
- end
27
-
28
- def self.opt?
29
- @opt
30
- end
31
-
24
+ # @param value [Integer]
25
+ # @return [true,false] integer value is within permitted range
32
26
  def self.in_range?(value)
33
27
  @type.class::RANGE.cover? value
34
28
  end
35
29
 
30
+ # @return [Integer]
31
+ def get
32
+ @value
33
+ end
34
+
35
+ # Set an Integer value
36
+ # @param value [Integer]
37
+ # @raise [RangeError] value is outside of range according to {::in_range?}
38
+ # @raise [TypeError]
36
39
  def set(value)
37
- if value
38
- if value.kind_of? Integer
39
- if self.class.in_range?(value)
40
- @value = value.to_i
41
- else
42
- raise Error.new "value out of range"
43
- end
40
+ if value.kind_of? Integer
41
+ if self.class.in_range?(value)
42
+ @value = value.to_i
44
43
  else
45
- raise Error.new "value must be an integer"
44
+ raise RangeError.new
46
45
  end
47
- elsif self.class.opt?
48
- @value = nil
49
46
  else
50
- raise Error.new "value unacceptable"
51
- end
52
- end
53
-
54
- def get
55
- @value
47
+ raise TypeError.new "value must be an integer"
48
+ end
56
49
  end
57
50
 
58
- # @param value [Integer, nil]
59
- def initialize(value)
60
- if value
61
- set(value)
62
- else
63
- @value = nil
64
- end
51
+ # @note calls {#set}(value)
52
+ def initialize(value)
53
+ set(value)
65
54
  end
66
55
 
67
-
68
56
  end
69
57
 
70
58
  class U8 < INTEGER
71
59
 
72
-
73
- def self.from_compact!(input)
74
- self.new(input.getU8!)
60
+ def self.from_compact!(input, stack)
61
+ value = input.getU8!
62
+ if value
63
+ self.new(value)
64
+ else
65
+ value
66
+ end
75
67
  end
76
68
 
77
69
  def to_compact(out)
78
70
  out.putU8(@value)
79
- end
80
-
71
+ end
81
72
 
82
73
  end
83
74
 
84
75
  class U16 < INTEGER
85
76
 
86
- def self.from_compact!(input)
87
- self.new(input.getU16!)
77
+ def self.from_compact!(input, stack)
78
+ value = input.getU16!
79
+ if value
80
+ self.new(value)
81
+ else
82
+ value
83
+ end
88
84
  end
89
85
 
90
86
  def to_compact(out)
@@ -95,8 +91,13 @@ module SlowBlink::Message
95
91
 
96
92
  class U32 < INTEGER
97
93
 
98
- def self.from_compact!(input)
99
- self.new(input.getU32!)
94
+ def self.from_compact!(input, stack)
95
+ value = input.getU32!
96
+ if value
97
+ self.new(value)
98
+ else
99
+ value
100
+ end
100
101
  end
101
102
 
102
103
  def to_compact(out)
@@ -107,8 +108,13 @@ module SlowBlink::Message
107
108
 
108
109
  class U64 < INTEGER
109
110
 
110
- def self.from_compact!(input)
111
- self.new(input.getU64!)
111
+ def self.from_compact!(input, stack)
112
+ value = input.getU64!
113
+ if value
114
+ self.new(value)
115
+ else
116
+ value
117
+ end
112
118
  end
113
119
 
114
120
  def to_compact(out)
@@ -119,8 +125,13 @@ module SlowBlink::Message
119
125
 
120
126
  class I8 < INTEGER
121
127
 
122
- def self.from_compact!(input)
123
- self.new(input.getI8!)
128
+ def self.from_compact!(input, stack)
129
+ value = input.getI8!
130
+ if value
131
+ self.new(value)
132
+ else
133
+ value
134
+ end
124
135
  end
125
136
 
126
137
  def to_compact(out)
@@ -131,8 +142,13 @@ module SlowBlink::Message
131
142
 
132
143
  class I16 < INTEGER
133
144
 
134
- def self.from_compact!(input)
135
- self.new(input.getI16!)
145
+ def self.from_compact!(input, stack)
146
+ value = input.getI16!
147
+ if value
148
+ self.new(value)
149
+ else
150
+ value
151
+ end
136
152
  end
137
153
 
138
154
  def to_compact(out)
@@ -143,8 +159,13 @@ module SlowBlink::Message
143
159
 
144
160
  class I32 < INTEGER
145
161
 
146
- def self.from_compact!(input)
147
- self.new(input.getI32!)
162
+ def self.from_compact!(input, stack)
163
+ value = input.getI32!
164
+ if value
165
+ self.new(value)
166
+ else
167
+ value
168
+ end
148
169
  end
149
170
 
150
171
  def to_compact(out)
@@ -155,8 +176,13 @@ module SlowBlink::Message
155
176
 
156
177
  class I64 < INTEGER
157
178
 
158
- def self.from_compact!(input)
159
- self.new(input.getI64!)
179
+ def self.from_compact!(input, stack)
180
+ value = input.getI64!
181
+ if value
182
+ self.new(value)
183
+ else
184
+ value
185
+ end
160
186
  end
161
187
 
162
188
  def to_compact(out)