slow_blink 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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)