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,99 +19,113 @@
19
19
 
20
20
  module SlowBlink
21
21
 
22
+ # This module is concerned with generating models from Schema that are optimised for encoding/decoding and enforcing constraints
22
23
  module Message
23
24
 
24
- class Error < Exception
25
+ class Error < StandardError
25
26
  end
26
27
 
27
28
  class Model
28
29
 
30
+ # the maximum level of nesting in messages able to be decoded by models
31
+ DEFAULT_MAX_RECURSION = 100
32
+
29
33
  # @api user
30
34
  #
31
35
  # Create a Model from a {Schema}
32
36
  #
33
37
  # @param schema [SlowBlink::Schema]
34
- def initialize(schema)
38
+ # @param opts [Hash]
39
+ #
40
+ # @option opts [Symbol] :maxRecursion
41
+ #
42
+ def initialize(schema, **opts)
43
+
35
44
  @schema = schema
36
- @taggedGroups = {}
37
- schema.tagged.each do |id, g|
38
- @taggedGroups[id] = _model_group(false, g)
39
- end
45
+ @taggedGroups = {}
46
+ @groups = {}
47
+ @maxRecursion = opts[:maxRecursion]||DEFAULT_MAX_RECURSION
48
+ maxRecursion = @maxRecursion
49
+
50
+ # define the extension object
51
+ groups = @groups
40
52
  taggedGroups = @taggedGroups
41
- @top = Class.new(DynamicGroup) do
53
+ permitted = schema.tagged.keys
54
+ @extensionObject = Class.new(DynamicGroup) do
55
+ @maxRecursion = maxRecursion
56
+ @extensionObject = self
42
57
  @opt = false
43
- @groups = taggedGroups
44
- @permitted = taggedGroups.keys
45
- end
58
+ @groups = groups
59
+ @taggedGroups = taggedGroups
60
+ @permitted = permitted
61
+ end
62
+ extensionObject = @extensionObject
63
+
64
+ schema.groups.each do |name, g|
65
+ this = self
66
+ @groups[name] = Class.new(Group) do
67
+ @extensionObject = extensionObject
68
+ @maxRecursion = maxRecursion
69
+ @name = g.nameWithID.name
70
+ @id = g.nameWithID.id
71
+ @fields = {}
72
+ g.fields.each do |f|
73
+ @fields[f.nameWithID.name] = this._model_field(f)
74
+ end
75
+ end
76
+ if g.nameWithID.id
77
+ @taggedGroups[g.nameWithID.id] = @groups[name]
78
+ end
79
+ end
80
+
81
+
46
82
  end
47
83
 
48
84
  # @api user
49
85
  #
50
86
  # Initialise a message model instance with a compact form string
51
87
  #
52
- # @note return value will be an *anonymous* *subclass* *instance* of {DynamicGroup}
88
+ # @note return value will be an *anonymous* *subclass* *instance* of {Group}
53
89
  #
54
- # @param [String] Blink Protocol compact form
55
- # @return [DynamicGroup] group instance
90
+ # @param input [String] Blink Protocol compact form
91
+ # @return [Group] group instance
56
92
  #
57
93
  def decode_compact(input)
58
- @top.from_compact!(input.dup)
59
- end
60
-
61
- # @api user
62
- #
63
- # Create an instance of a group subclass
64
- #
65
- # @note return value will be an *anonymous* *subclass* *instance* of {DynamicGroup} or {StaticGroup}
66
- #
67
- # @overload group(name, data)
68
- #
69
- # @param name [String] name of group
70
- # @param data [Hash] bulk initialisation data
71
- # @return [StaticGroup,DynamicGroup] group instance
72
- #
73
- # @overload group(name)
74
- #
75
- # @param name [String] name of group
76
- # @yield [Group] group instance to initalise
77
- # @return [StaticGroup,DynamicGroup] group instance
78
- #
79
- def group(name, data=nil, &block)
80
- group = @top.groups.values.detect{|g|g.name == name}
81
- if group
82
- top = @top.new(group.new(data))
83
- if block
84
- self.instance_exec(top, &block)
85
- # validate optional constraint here
94
+ stack = []
95
+ inputSize = input.size
96
+ buf = input.getBinary!
97
+ if buf.size > 0
98
+ id = buf.getU64!
99
+ groupClass = @taggedGroups[id]
100
+ begin
101
+ if groupClass
102
+ group = groupClass.from_compact!(buf, stack)
103
+ else
104
+ raise Error.new "W2: Group id #{group.id} is unknown"
105
+ end
106
+ rescue Error => ex
107
+ puts ex
108
+ puts "encountered at offset #{inputSize - input.size}"
109
+ puts stack.last.name
110
+ raise
86
111
  end
87
- top
88
112
  else
89
- raise
113
+ raise Error.new "W1: Top level group cannot be null "
90
114
  end
91
115
  end
92
116
 
93
- # @api private
117
+ # @api user
94
118
  #
95
- # Create a model for a Group
119
+ # Get a group model
96
120
  #
97
- # @param opt [true,false] this group is allowed to be optional
98
- # @param group [SlowBlink::Group] group definition
99
- # @return [Class] anonymous subclass of {StaticGroup}
100
- def _model_group(opt, group)
101
- this = self
102
- Class.new(StaticGroup) do
103
- @implements = group.class
104
- @name = group.nameWithID.name
105
- @id = group.nameWithID.id
106
- @opt = opt
107
- @fields = group.fields.inject([]) do |fields, f|
108
- fields << this._model_field(f)
109
- end
110
-
111
- end
121
+ # @param name [String] name of group (may be qualified)
122
+ # @return [Class] {DynamicGroup} or {Group}
123
+ # @return [nil] group not defined
124
+ #
125
+ def group(name)
126
+ @groups[name]
112
127
  end
113
128
 
114
-
115
129
  # @api private
116
130
  #
117
131
  # Create a model for a Field
@@ -121,11 +135,10 @@ module SlowBlink
121
135
  def _model_field(field)
122
136
  this = self
123
137
  Class.new(Field) do
124
- @implements = field.class
125
138
  @opt = field.opt?
126
139
  @name = field.nameWithID.name
127
140
  @id = field.nameWithID.id
128
- @type = this._model_type(field)
141
+ @type = this._model_type(field.type, field.opt?)
129
142
  end
130
143
  end
131
144
 
@@ -133,50 +146,71 @@ module SlowBlink
133
146
  #
134
147
  # Create a model for a type
135
148
  #
136
- # @param field [SlowBlink::Field] field definition (containing type)
137
- # @return [Class] anonymous subclass
138
- def _model_type(field)
139
- type = field.type
140
- name = field.nameWithID.name
149
+ # @param type [SlowBlink::Type] type definition
150
+ # @param opt [true,false] parent definition may allow this type to be optional
151
+ #
152
+ #
153
+ def _model_type(type, opt)
154
+ this = self
155
+ extensionObject = @extensionObject
156
+ maxRecursion = @maxRecursion
141
157
  case type.class
142
158
  when SlowBlink::OBJECT
143
159
  groups = @groups
144
- permitted = @schema.tagged.keys
145
- klass = Class.new(DynamicGroup) do
146
- @implements = type.class
147
- @opt = field.opt?
160
+ taggedGroups = @taggedGroups
161
+ permitted = @taggedGroups.keys
162
+ Class.new(DynamicGroup) do
163
+ @extensionObject = extensionObject
164
+ @maxRecursion = maxRecursion
165
+ @opt = opt
148
166
  @groups = groups
167
+ @taggedGroups = taggedGroups
149
168
  @permitted = permitted
150
- end
169
+ end
151
170
  when SlowBlink::REF
152
- if type.ref.kind_of? Group
171
+ if type.ref.kind_of? SlowBlink::Group
153
172
  if type.dynamic?
173
+ taggedGroups = @taggedGroups
154
174
  groups = @groups
155
- permitted = @schema.tagged.keys
175
+ permitted = @taggedGroups.keys
156
176
  @schema.tagged.each do |id, g|
157
177
  if g.group_kind_of?(type)
158
178
  permitted << id
159
179
  end
160
180
  end
161
181
  Class.new(DynamicGroup) do
162
- @implements = type.class
163
- @name = name
164
- @opt = field.opt?
182
+ @extensionObject = extensionObject
183
+ @maxRecursion = maxRecursion
184
+ @opt = opt
185
+ @taggedGroups = taggedGroups
165
186
  @groups = groups
166
187
  @permitted = permitted
167
188
  end
168
189
  else
169
- _model_group(field.opt?, type.ref)
190
+ Class.new(StaticGroup) do
191
+ @extensionObject = extensionObject
192
+ @maxRecursion = maxRecursion
193
+ @name = type.ref.nameWithID.name
194
+ @id = nil
195
+ @opt = opt
196
+ @fields = {}
197
+ type.ref.fields.each do |f|
198
+ @fields[f.nameWithID.name] = this._model_field(f)
199
+ end
200
+ end
170
201
  end
171
202
  else
172
- _model_type(opt, type.ref)
203
+ _model_type(type.ref)
173
204
  end
174
- else
205
+ when SlowBlink::SEQUENCE
206
+ Class.new(SEQUENCE) do
207
+ @maxRecursion = maxRecursion
208
+ @type = this._model_type(type.type, false)
209
+ end
210
+ else
175
211
  Class.new(SlowBlink::Message.const_get(type.class.name.split('::').last)) do
176
- klass =
177
- @opt = field.opt?
178
- @name = name
179
- @type = type
212
+ @opt = opt
213
+ @type = type
180
214
  end
181
215
  end
182
216
  end
@@ -187,6 +221,7 @@ module SlowBlink
187
221
 
188
222
  end
189
223
 
224
+ require "slow_blink/message/ext_compact_encoder"
190
225
  require "slow_blink/message/field"
191
226
  require "slow_blink/message/integer"
192
227
  require "slow_blink/message/string"
@@ -200,4 +235,5 @@ require "slow_blink/message/group"
200
235
  require "slow_blink/message/time"
201
236
  require "slow_blink/message/time_of_day"
202
237
  require "slow_blink/message/date"
238
+ require "slow_blink/message/decimal"
203
239
 
@@ -20,56 +20,71 @@
20
20
  module SlowBlink::Message
21
21
 
22
22
  class SEQUENCE
23
+
24
+ # @return the type that repeats in this SEQUENCE
25
+ def self.type
26
+ @type
27
+ end
23
28
 
24
- def self.from_compact!(input)
29
+ def self.from_compact!(input, stack)
30
+
31
+ if stack.size < @maxRecursion
32
+ stack << self
33
+ else
34
+ raise Error.new "stack limit"
35
+ end
36
+
25
37
  value = []
26
38
  size = input.getU32!
27
39
  if size
28
40
  while out.size < size do
29
- value << @type.from_compact!(input)
41
+ value << @type.from_compact!(input, stack)
30
42
  end
31
43
  self.new(value)
32
44
  else
33
- self.new
45
+ nil
34
46
  end
35
- end
36
47
 
37
- # @param v [Array,nil]
38
- def set(value)
39
- if value
40
- if value.kind_of? Array
41
- @value = v
42
- else
43
- raise Error.new "bad type"
44
- end
45
- elsif self.class.opt?
46
- @value = nil
47
- else
48
- raise Error.new "value unacceptable"
49
- end
48
+ stack.pop
49
+
50
50
  end
51
51
 
52
+ # @return [Array]
52
53
  def get
53
54
  @value
54
55
  end
55
56
 
57
+ # Set value of a SEQUENCE
58
+ #
56
59
  # @param value [Array]
57
- def initialize(value)
58
- if value
59
- set(value)
60
+ # @raise [TypeError]
61
+ # @raise [RangeError]
62
+ #
63
+ def set(value)
64
+ @value = []
65
+ if value.kind_of? Array
66
+ value.each do |v|
67
+ if v.kind_of? self.class.type
68
+ @value << v
69
+ else
70
+ @value << self.class.type.new(v)
71
+ end
72
+ end
60
73
  else
61
- @value = nil
74
+ raise TypeError.new "expecting an array"
62
75
  end
63
76
  end
64
77
 
78
+ # @note calls {#set}(value)
79
+ def initialize(value)
80
+ set(value)
81
+ end
82
+
65
83
  def to_compact(out)
66
- if @value
67
- @value.inject(out.putU32(@value.size)) do |out, v|
68
- out << v.to_compact
69
- end
70
- else
71
- out.putU32(nil)
72
- end
84
+ out.putU32(@value.size)
85
+ @value.each do |value|
86
+ value.to_compact(out)
87
+ end
73
88
  end
74
89
 
75
90
  end
@@ -21,15 +21,7 @@ module SlowBlink::Message
21
21
 
22
22
  class STRING
23
23
 
24
- def self.name
25
- @name
26
- end
27
-
28
- def self.opt?
29
- @opt
30
- end
31
-
32
- def self.from_compact!(input)
24
+ def self.from_compact!(input, stack)
33
25
  value = input.getString!
34
26
  if value
35
27
  if !@type.size or value.size <= @type.size
@@ -37,52 +29,46 @@ module SlowBlink::Message
37
29
  else
38
30
  raise Error.new "W7: String value exceeds maximum size"
39
31
  end
40
- elsif @opt
41
- self.new(nil)
42
32
  else
43
- raise Error.new "W5: Value cannot be null"
44
- end
33
+ value
34
+ end
45
35
  end
46
36
 
37
+ # @return [Integer,nil] maximum size of string in bytes
47
38
  def self.size
48
39
  @type.size
49
40
  end
50
41
 
42
+ # @return [String]
51
43
  def get
52
44
  @value
53
45
  end
54
46
 
47
+ # Set a string value
48
+ # @param value [String]
49
+ # @raise [TypeError]
50
+ # @raise [RangeError] value is larger than {::size}
55
51
  def set(value)
56
- if value
57
- if value.kind_of? String
58
- if !self.class.size or value.size <= self.class.size
59
- @value = value
60
- else
61
- raise Error.new "string cannot be larger than #{self.class.size} bytes"
62
- end
52
+ if value.kind_of? String
53
+ if !self.class.size or value.size <= self.class.size
54
+ @value = value.to_s
63
55
  else
64
- raise Error.new "expecting a string type"
56
+ raise RangeError.new "String instance cannot be larger than #{self.class.size} bytes"
65
57
  end
66
- elsif self.class.opt?
67
- @value = nil
68
58
  else
69
- raise Error.new "string cannot be null"
70
- end
59
+ raise TypeError.new "expecting a String instance"
60
+ end
71
61
  end
72
62
 
63
+ # @note calls {#set}(value)
73
64
  def initialize(value)
74
- @value = nil
75
- if value
76
- set(value)
77
- end
65
+ set(value)
78
66
  end
79
67
 
80
68
  def to_compact(out)
81
69
  out.putString(@value)
82
70
  end
83
71
 
84
-
85
-
86
72
  end
87
73
 
88
74
  end
@@ -17,46 +17,84 @@
17
17
  # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
18
  # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19
19
 
20
- module SlowBlink::Message
20
+ require 'time'
21
21
 
22
+ module SlowBlink::Message
22
23
 
23
24
  class MILLI_TIME
24
25
 
25
- def self.from_compact!(input)
26
- self.new(input.getI64!)
27
- end
28
-
29
- def set(value)
26
+ def self.from_compact!(input, stack)
27
+ value = input.getI64!
30
28
  if value
31
- raise
32
- elsif self.class.opt?
33
- @value = nil
29
+ self.new(value)
34
30
  else
35
- raise
31
+ value
36
32
  end
37
33
  end
38
34
 
35
+ # @return [DateTime]
39
36
  def get
40
37
  @value
41
38
  end
42
39
 
43
- def initialize(value)
44
- if value
45
- set(value)
40
+ # @overload set(value)
41
+ # Set a millisecond time value
42
+ # @param value [Time,DateTime,Date]
43
+ # @overload set(value)
44
+ # @param value [String] time in ISO 8601
45
+ # @overload set(value)
46
+ # @param value [String] time in milliseconds since UNIX epoch
47
+ # @raise [TypeError]
48
+ # @raise [ArgumentError]
49
+ def set(value)
50
+ if value.kind_of? Time or value.kind_of? DateTime or value.kind_of? Date
51
+ @value = time.to_datetime
52
+ elsif value.kind_of? String
53
+ @value = DateTime.parse(value)
54
+ elsif value.kind_of? Integer
55
+ @value = DateTime.new(value)
46
56
  else
47
- @value = nil
48
- end
57
+ raise TypeError
58
+ end
49
59
  end
50
60
 
51
- def to_compact(out)
52
- out.putI64(@value)
61
+ # @note calls {#set}(value)
62
+ def initialize(value)
63
+ set(value)
53
64
  end
54
65
 
66
+ def to_compact(out)
67
+ out.putI64(@value.strftime('%Q'))
68
+ end
55
69
 
56
70
  end
57
71
 
58
72
  class NANO_TIME < MILLI_TIME
59
73
 
74
+ def to_compact(out)
75
+ out.putI64(@value.strftime('%N'))
76
+ end
77
+
78
+ # @overload set(value)
79
+ # Set a nanosecond resolution time value
80
+ # @param value [Time,DateTime,Date]
81
+ # @overload set(value)
82
+ # @param value [String] time in ISO 8601
83
+ # @overload set(value)
84
+ # @param value [Integer] time in nanoseconds since UNIX epoch
85
+ # @raise [TypeError]
86
+ # @raise [ArgumentError]
87
+ def set(value)
88
+ if value.kind_of? Time or value.kind_of? DateTime or value.kind_of? Date
89
+ @value = time.to_datetime
90
+ elsif value.kind_of? String
91
+ @value = DateTime.parse(value)
92
+ elsif value.kind_of? Integer
93
+ @value = DateTime.new(value)
94
+ else
95
+ raise TypeError
96
+ end
97
+ end
60
98
 
61
99
  end
62
100