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,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