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.
- checksums.yaml +4 -4
- data/ext/slow_blink/ext_schema_parser/lexer.c +168 -177
- data/ext/slow_blink/ext_schema_parser/lexer.h +1 -1
- data/ext/slow_blink/ext_schema_parser/parser.c +303 -306
- data/ext/slow_blink/ext_schema_parser/parser.h +11 -12
- data/ext/slow_blink/{ext_compact_encoder → message/ext_compact_encoder}/ext_compact_encoder.c +3 -1
- data/ext/slow_blink/message/ext_compact_encoder/extconf.rb +4 -0
- data/lib/slow_blink/error.rb +1 -1
- data/lib/slow_blink/message/binary.rb +19 -26
- data/lib/slow_blink/message/boolean.rb +17 -17
- data/lib/slow_blink/message/date.rb +24 -0
- data/lib/slow_blink/message/decimal.rb +34 -0
- data/lib/slow_blink/message/enumeration.rb +24 -23
- data/lib/slow_blink/message/field.rb +45 -6
- data/lib/slow_blink/message/fixed.rb +35 -25
- data/lib/slow_blink/message/floating_point.rb +16 -17
- data/lib/slow_blink/message/group.rb +242 -136
- data/lib/slow_blink/message/integer.rb +77 -51
- data/lib/slow_blink/message/model.rb +121 -85
- data/lib/slow_blink/message/sequence.rb +43 -28
- data/lib/slow_blink/message/string.rb +17 -31
- data/lib/slow_blink/message/time.rb +55 -17
- data/lib/slow_blink/message/time_of_day.rb +40 -50
- data/lib/slow_blink/schema.rb +17 -6
- data/lib/slow_blink/version.rb +1 -1
- data/rakefile +12 -7
- data/test/tc_incr_annote.rb +16 -0
- data/test/tc_inputs.rb +2 -2
- data/test/tc_model_dynamic_group.rb +105 -0
- data/test/tc_model_extension.rb +143 -0
- data/test/tc_model_static_group.rb +27 -36
- data/test/tc_model_think_blink.rb +18 -35
- metadata +12 -9
- data/ext/slow_blink/ext_compact_encoder/extconf.rb +0 -4
- /data/ext/slow_blink/{ext_compact_encoder → message/ext_compact_encoder}/compact_encoder.c +0 -0
- /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 <
|
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
|
-
|
38
|
+
# @param opts [Hash]
|
39
|
+
#
|
40
|
+
# @option opts [Symbol] :maxRecursion
|
41
|
+
#
|
42
|
+
def initialize(schema, **opts)
|
43
|
+
|
35
44
|
@schema = schema
|
36
|
-
@taggedGroups = {}
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
53
|
+
permitted = schema.tagged.keys
|
54
|
+
@extensionObject = Class.new(DynamicGroup) do
|
55
|
+
@maxRecursion = maxRecursion
|
56
|
+
@extensionObject = self
|
42
57
|
@opt = false
|
43
|
-
@groups =
|
44
|
-
@
|
45
|
-
|
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 {
|
88
|
+
# @note return value will be an *anonymous* *subclass* *instance* of {Group}
|
53
89
|
#
|
54
|
-
# @param [String] Blink Protocol compact form
|
55
|
-
# @return [
|
90
|
+
# @param input [String] Blink Protocol compact form
|
91
|
+
# @return [Group] group instance
|
56
92
|
#
|
57
93
|
def decode_compact(input)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
117
|
+
# @api user
|
94
118
|
#
|
95
|
-
#
|
119
|
+
# Get a group model
|
96
120
|
#
|
97
|
-
# @param
|
98
|
-
# @
|
99
|
-
# @return [
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
137
|
-
# @
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
147
|
-
@
|
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 = @
|
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
|
-
@
|
163
|
-
@
|
164
|
-
@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
|
-
|
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(
|
203
|
+
_model_type(type.ref)
|
173
204
|
end
|
174
|
-
|
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
|
-
|
177
|
-
@
|
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
|
-
|
45
|
+
nil
|
34
46
|
end
|
35
|
-
end
|
36
47
|
|
37
|
-
|
38
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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.
|
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
|
-
|
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.
|
58
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
27
|
-
end
|
28
|
-
|
29
|
-
def set(value)
|
26
|
+
def self.from_compact!(input, stack)
|
27
|
+
value = input.getI64!
|
30
28
|
if value
|
31
|
-
|
32
|
-
elsif self.class.opt?
|
33
|
-
@value = nil
|
29
|
+
self.new(value)
|
34
30
|
else
|
35
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
-
|
48
|
-
end
|
57
|
+
raise TypeError
|
58
|
+
end
|
49
59
|
end
|
50
60
|
|
51
|
-
|
52
|
-
|
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
|
|