bare-rb 0.1.2 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bdfcd51a2a757a2cb2c532d1281afe227150518a6feb625466a5f25fac16b84b
4
- data.tar.gz: ca3ed0f25556c996c62ff70a31ef3c50d9a3e6cad0d712eb8e428013a5af0af3
3
+ metadata.gz: bbfb767287130c73249bf016a4bc3b4d9b6bd44e82ce8e7d9e3557ee6c58e7b3
4
+ data.tar.gz: aee2d8ab8457356c7e544dbd31c6f81d05c9655ab3138ddfe7315ab221d16ea0
5
5
  SHA512:
6
- metadata.gz: 222f83030bee145a42b3ae2ef890316f4969502f3a8e9704e3b84ffadaab0866d71163bf97b0d3f90ec02a065336343b20149375b9e628c646d8b342d0e5b27d
7
- data.tar.gz: e757dbf048dd0f65392561a9034b3407d2fe9e2e942bcf181b3c7a25847a03e9eb515b66ef74b23115b37a94751daeae04eb4190b218a0c2dcbe2ab988ca3256
6
+ metadata.gz: e1d6c44757bcdd9f6811bf0107e7b13d68703e6616faef2029b09f2273ae75168eff02091f13cddfd6c5e5b9499db5efe2d7cf72d88c3c0e001a0da00b4c602f
7
+ data.tar.gz: e38a97985800775f0aa995db17ad9b8ea6a1294e12f0e0fd5cc60008359d286af6ec173bad63d190436bbd0bea876ff590e0a1fcac6099bd394ce9790bac1798
data/lib/bare-rb.rb CHANGED
@@ -2,22 +2,65 @@ require 'set'
2
2
  require_relative "types"
3
3
  require_relative "lexer"
4
4
  require_relative "parser"
5
+ require_relative 'dfs'
6
+ require_relative 'generative_testing/gen'
7
+ require_relative 'generative_testing/monkey_patch'
5
8
 
6
9
  class Bare
10
+ def self.encode(msg, schema, type = nil)
11
+ buffer = "".b
12
+ if schema.is_a?(BareTypes::Schema)
13
+ raise NoTypeProvided.new("To encode with a schema as opposed to a raw type you must specify which type in the schema you want to encode as a symbol.\nBare.encode(msg, schema, :Type)") if type.nil?
14
+ unless schema.types.include?(type)
15
+ raise("#{type} is not a type found in this schema. Choose from #{schema.types.keys}")
16
+ end
17
+ schema[type].encode(msg, buffer)
18
+ else
19
+ schema.encode(msg, buffer)
20
+ end
21
+ buffer
22
+ end
23
+
24
+ def self.decode(msg, schema, type = nil)
25
+ if schema.is_a?(BareTypes::Schema)
26
+ raise NoTypeProvided.new("To decode with a schema as opposed to a raw type you must specify which type in the same you want to encode as a symbol.\nBare.encode(msg, schema, :Type)") if type.nil?
27
+ value, _ = schema[type].decode(msg)
28
+ value
29
+ else
30
+ value, _ = schema.decode(msg)
31
+ value
32
+ end
33
+ end
34
+
35
+ # Returns a schema and a binary input
36
+ # optionally write these to files
37
+ def self.generative_test(schema_path=nil, binary_path=nil)
38
+ schema = BareTypes::Schema.make
39
+ input = schema.create_input
40
+ key = schema.types.keys[0]
41
+ binary = Bare.encode(input[key], schema, key)
42
+ unless binary_path.nil?
43
+ file = File.open(binary_path, 'wb+')
44
+ file.write(binary)
45
+ file.close
46
+ end
47
+ unless schema_path.nil?
48
+ file = File.open(schema_path, 'w+')
49
+ file.write(schema.to_s)
50
+ file.close
51
+ end
52
+ return schema, binary, key
53
+ end
7
54
 
8
55
  def self.parse_schema(path)
9
- # Hash of class names to BARE ASTs
56
+ # Hash of class names to BARE types
10
57
  # Eg. types['Customer'] == Bare.i32
11
- types = parser(lexer(path))
12
- return types
13
- end
14
-
15
- def self.encode(msg, schema)
16
- return schema.encode(msg)
58
+ parsed = parser(lexer(path))
59
+ Bare.Schema(parsed)
17
60
  end
18
61
 
19
- def self.decode(msg, schema)
20
- return schema.decode(msg)[:value]
62
+ def self.Schema(hash)
63
+ BareTypes::Schema.new(hash)
21
64
  end
22
65
 
23
66
  # These classes are wrapped in methods for ergonomics.
@@ -119,4 +162,3 @@ class Bare
119
162
  return BareTypes::Enum.new(*opts)
120
163
  end
121
164
  end
122
-
data/lib/exceptions.rb CHANGED
@@ -5,6 +5,36 @@ class BareException < StandardError
5
5
  end
6
6
  end
7
7
 
8
+ class InvalidBool < BareException
9
+ def initialize(msg = nil)
10
+ super
11
+ end
12
+ end
13
+
14
+ class CircularSchema < BareException
15
+ def initialize(msg = nil)
16
+ super
17
+ end
18
+ end
19
+
20
+ class ReferenceException < BareException
21
+ def initialize(msg=nil)
22
+ super
23
+ end
24
+ end
25
+
26
+ class FixedDataSizeWrong < BareException
27
+ def initialize(msg=nil)
28
+ super
29
+ end
30
+ end
31
+
32
+ class NoTypeProvided < BareException
33
+ def initialize(msg = nil)
34
+ super
35
+ end
36
+ end
37
+
8
38
  class SchemaParsingException < BareException
9
39
  def initialize(msg=nil)
10
40
  super
@@ -0,0 +1,26 @@
1
+ require_relative '../bare-rb'
2
+ require_relative './monkey_patch'
3
+ require_relative './grammar_util'
4
+
5
+ def get_type(depth, names = [], can_be_symbol = true)
6
+ if names.size == 0
7
+ can_be_symbol = false
8
+ end
9
+ terminators = [BareTypes::Data, BareTypes::DataFixedLen,
10
+ BareTypes::U8, BareTypes::U16, BareTypes::U32, BareTypes::U64,
11
+ BareTypes::I8, BareTypes::I16, BareTypes::I32, BareTypes::I64,
12
+ BareTypes::F32, BareTypes::F64]
13
+ aggregates = [BareTypes::Array, BareTypes::ArrayFixedLen,
14
+ BareTypes::Struct]
15
+
16
+ all = terminators + aggregates
17
+
18
+ # 1/5 changes of a reference
19
+ if rand(5) == 0 && names.size != 1 && can_be_symbol
20
+ names[rand(names.size)]
21
+ elsif depth >= 10 # if depth >= 10 only use terminating types
22
+ all[rand(terminators.size)].make(depth + 1, names)
23
+ else
24
+ all[rand(all.size)].make(depth + 1, names)
25
+ end
26
+ end
@@ -0,0 +1,22 @@
1
+ def create_user_type_name
2
+ upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3
+ lower = upper.downcase
4
+ digit = "0123456789"
5
+
6
+ name = upper[rand(upper.size)]
7
+ loop do
8
+ if rand(50) < 5
9
+ break
10
+ end
11
+ num = rand(3)
12
+ if num == 0
13
+ name << upper[rand(upper.size)]
14
+ elsif num == 1
15
+ name << lower[rand(lower.size)]
16
+ else
17
+ name << digit[rand(digit.size)]
18
+ end
19
+ end
20
+ name.to_sym
21
+ end
22
+
@@ -0,0 +1,262 @@
1
+ require_relative '../bare-rb'
2
+ require_relative './grammar_util'
3
+
4
+ # 10MB max data size
5
+ DATA_MAX_SIZE = 30000
6
+ ARRAY_MAX_SIZE = 40
7
+ STRUCT_FIELDS_MAX = 5
8
+
9
+ # Monkey patch every bare class to include make and create_input
10
+ # make - a factory to create a random variant of the bare class
11
+ # create_input - creates an input that could be used with Bare.Encode for this type
12
+
13
+ class BareTypes::Reference
14
+ def create_input
15
+ self.ref.create_input
16
+ end
17
+
18
+ def self.make(depth, names)
19
+ self.ref.make(depth, names)
20
+ end
21
+ end
22
+
23
+ # region Integers
24
+ class BareTypes::U8
25
+ def self.make(depth, names)
26
+ BareTypes::U8.new
27
+ end
28
+
29
+ def create_input
30
+ rand(256)
31
+ end
32
+ end
33
+
34
+ class BareTypes::U16
35
+ def self.make(depth, names)
36
+ BareTypes::U16.new
37
+ end
38
+
39
+ def create_input
40
+ rand(2 ** 16)
41
+ end
42
+ end
43
+
44
+ class BareTypes::U32
45
+ def self.make(depth, names)
46
+ BareTypes::U32.new
47
+ end
48
+
49
+ def create_input
50
+ rand(2 ** 32)
51
+ end
52
+ end
53
+
54
+ class BareTypes::U64
55
+ def self.make(depth, names)
56
+ BareTypes::U64.new
57
+ end
58
+
59
+ def create_input
60
+ rand(2 ** 64)
61
+ end
62
+ end
63
+
64
+ class BareTypes::I8
65
+ def self.make(depth, names)
66
+ BareTypes::I8.new
67
+ end
68
+
69
+ def create_input
70
+ rand(2 ** 8) - (2 ** 7)
71
+ end
72
+ end
73
+
74
+ class BareTypes::I16
75
+ def self.make(depth, names)
76
+ BareTypes::I16.new
77
+ end
78
+
79
+ def create_input
80
+ rand(2 ** 16) - (2 ** 15)
81
+ end
82
+ end
83
+
84
+ class BareTypes::I32
85
+ def self.make(depth, names)
86
+ BareTypes::I32.new
87
+ end
88
+
89
+ def create_input
90
+ rand(2 ** 32) - (2 ** 31)
91
+ end
92
+ end
93
+
94
+ class BareTypes::I64
95
+ def self.make(depth, names)
96
+ BareTypes::I64.new
97
+ end
98
+
99
+ def create_input
100
+ rand(2 ** 64) - (2 ** 63)
101
+ end
102
+ end
103
+
104
+ # endregion
105
+
106
+ #region Floats
107
+ class BareTypes::F32
108
+ def self.make(depth, names)
109
+ self.new
110
+ end
111
+
112
+ def create_input
113
+ float = nil
114
+ loop do
115
+ input = [rand(266), rand(266), rand(266), rand(266)]
116
+ float = input.pack("cccc").unpack('e')
117
+ if float[0] == float[0] && !float[0].nan?
118
+ break
119
+ end
120
+ end
121
+ float[0]
122
+ end
123
+ end
124
+
125
+ class BareTypes::F64
126
+ def self.make(depth, names)
127
+ self.new
128
+ end
129
+
130
+ def create_input
131
+ float = nil
132
+ loop do
133
+ input = [rand(266), rand(266), rand(266), rand(266), rand(266), rand(266), rand(266), rand(266)]
134
+ float = input.pack("cccccccc").unpack('E')
135
+ if float[0] == float[0] && !float[0].nan?
136
+ break
137
+ end
138
+ end
139
+ float[0]
140
+ end
141
+ end
142
+
143
+ #endregion
144
+
145
+ #region Data
146
+ class BareTypes::DataFixedLen
147
+ def self.make(depth, names)
148
+ length = rand(max = DATA_MAX_SIZE) + 1
149
+ self.new(length)
150
+ end
151
+
152
+ def create_input
153
+ # 100 random bytes
154
+ arr = []
155
+ 0.upto(length - 1).each do |i|
156
+ arr << i % 256
157
+ end
158
+ arr.pack('c*')
159
+ end
160
+ end
161
+
162
+ class BareTypes::Data
163
+ def self.make(depth, names)
164
+ self.new
165
+ end
166
+
167
+ def create_input
168
+ arr = []
169
+ 0.upto(rand(DATA_MAX_SIZE)).each do |i|
170
+ arr << i % 256
171
+ end
172
+ arr.pack('c*')
173
+ end
174
+ end
175
+
176
+ #endregion
177
+
178
+ #region Array
179
+ class BareTypes::Array
180
+ def self.make(depth, names)
181
+ BareTypes::Array.new(get_type(depth + 1, names))
182
+ end
183
+
184
+ def create_input
185
+ count = rand(ARRAY_MAX_SIZE) + 1
186
+ arr = []
187
+ 0.upto(count) do
188
+ arr << @type.create_input
189
+ end
190
+ arr
191
+ end
192
+ end
193
+
194
+ class BareTypes::ArrayFixedLen
195
+ def self.make(depth, names)
196
+ self.new(get_type(depth + 1, names,), rand(ARRAY_MAX_SIZE) + 1)
197
+ end
198
+
199
+ def create_input
200
+ arr = []
201
+ 0.upto(@size - 1) do
202
+ arr << @type.create_input
203
+ end
204
+ arr
205
+ end
206
+ end
207
+
208
+ #endregion
209
+
210
+ #region Agg Types
211
+
212
+ class BareTypes::Struct
213
+ def self.make(depth, names)
214
+ hash = {}
215
+ 0.upto(rand(STRUCT_FIELDS_MAX) + 1) do
216
+ hash[create_user_type_name.to_sym] = get_type(depth + 1, names)
217
+ end
218
+ self.new(hash)
219
+ end
220
+
221
+ def create_input
222
+ input = {}
223
+ @mapping.keys.each do |name|
224
+ input[name] = @mapping[name].create_input
225
+ end
226
+ input
227
+ end
228
+ end
229
+
230
+ # endregion
231
+
232
+ class BareTypes::Schema
233
+ def create_input
234
+ input = {}
235
+ @types.each do |key, type|
236
+ input[key] = type.create_input
237
+ end
238
+ input
239
+ end
240
+
241
+ def self.make
242
+ schema = nil
243
+ loop do
244
+ names = []
245
+ schema = {}
246
+ 0.upto(rand(10)+1) do
247
+ names << create_user_type_name.to_sym
248
+ end
249
+ names.each do |name|
250
+ without_this_name = names.select { |n| n != name }
251
+ schema[name] = get_type(0, without_this_name, false)
252
+ end
253
+ begin
254
+ schema = Bare.Schema(schema)
255
+ rescue CircularSchema
256
+ next
257
+ end
258
+ break
259
+ end
260
+ schema
261
+ end
262
+ end
data/lib/lexer.rb CHANGED
@@ -3,55 +3,56 @@ require_relative './exceptions'
3
3
  def lexer(path)
4
4
  tokens = []
5
5
  line_num = 0
6
- File.open(path).each do |line|
6
+ file = File.open(path)
7
+ file.each do |line|
7
8
  while line.size > 0
8
9
  if /^#/.match(line)
9
10
  break
10
11
  elsif /^\n/.match(line)
11
12
  break
12
13
  elsif /^ /.match(line)
13
- line = line[1..]
14
+ line = line[1..line.size]
14
15
  elsif /^</.match(line)
15
- line = line[1..]
16
+ line = line[1..line.size]
16
17
  tokens << :less_than
17
18
  elsif /^>/.match(line)
18
- line = line[1..]
19
+ line = line[1..line.size]
19
20
  tokens << :greater_than
20
21
  next
21
22
  elsif /^{/.match(line)
22
- line = line[1..]
23
+ line = line[1..line.size]
23
24
  tokens << :open_block
24
25
  elsif /^=/.match(line)
25
- line = line[1..]
26
+ line = line[1..line.size]
26
27
  tokens << :equal
27
28
  elsif /^}/.match(line)
28
- line = line[1..]
29
+ line = line[1..line.size]
29
30
  tokens << :close_block
30
31
  elsif /^\[/.match(line)
31
- line = line[1..]
32
+ line = line[1..line.size]
32
33
  tokens << :open_brace
33
34
  elsif /^\]/.match(line)
34
- line = line[1..]
35
+ line = line[1..line.size]
35
36
  tokens << :close_brace
36
37
  elsif /^\(/.match(line)
37
- line = line[1..]
38
+ line = line[1..line.size]
38
39
  tokens << :open_paren
39
40
  elsif /^\)/.match(line)
40
- line = line[1..]
41
+ line = line[1..line.size]
41
42
  tokens << :close_paren
42
43
  elsif /^\|/.match(line)
43
- line = line[1..]
44
+ line = line[1..line.size]
44
45
  tokens << :bar
45
46
  elsif match = /^([0-9]+)/.match(line)
46
47
  tokens << match[0].to_i
47
- line = line[(match.size + 1)..]
48
+ line = line[(match[0].size)..line.size]
48
49
  next
49
- elsif match = /^[a-z,A-Z,_][_,a-z,A-Z,0-9]+/.match(line)
50
+ elsif match = /^[a-z,A-Z,_][_,a-z,A-Z,0-9]*/.match(line)
50
51
  tokens << match[0]
51
- line = line[(match[0].size)..]
52
+ line = line[(match[0].size)..line.size]
52
53
  elsif /:/.match(line)
53
54
  tokens << :colon
54
- line = line[1..]
55
+ line = line[1..line.size]
55
56
  else
56
57
  raise SchemaParsingException.new("Unable to lex line #{line_num} near #{line.inspect}")
57
58
  end
data/lib/parser.rb CHANGED
@@ -38,55 +38,103 @@ class Parser
38
38
  name = tokens[0]
39
39
  int_repr = tokens[2]
40
40
  enum_hash[int_repr] = name
41
- tokens = tokens[3..]
41
+ tokens = tokens[3..tokens.size]
42
42
  else
43
43
  enum_hash[count] = tokens[0]
44
44
  count += 1
45
- tokens = tokens[1..]
45
+ tokens = tokens[1..tokens.size]
46
46
  end
47
47
  end
48
48
  enum = Bare.Enum(enum_hash)
49
- return tokens[1..], enum
49
+ return tokens[1..tokens.size], enum
50
+ end
51
+
52
+ def parse_union(tokens)
53
+ count = 0
54
+ union_hash = {}
55
+ # type A_UNION ( int | uint | data = 7 | f32 )
56
+ while tokens[0] != :close_paren
57
+ if tokens[0] == :bar
58
+ tokens = tokens[1..tokens.size]
59
+ else
60
+ if tokens[1] == :equal
61
+ raise SchemaParsingException.new("Equals sign in union must be followed by a number") unless tokens[2].is_a?(Numeric)
62
+ count = tokens[2]
63
+ tokens, type = self.parse(tokens)
64
+ tokens = tokens[2..tokens.size]
65
+ union_hash[count] = type
66
+ count += 1
67
+ else
68
+ tokens, type = self.parse(tokens)
69
+ union_hash[count] = type
70
+ count += 1
71
+ end
72
+ end
73
+ end
74
+ return tokens, union_hash
50
75
  end
51
76
 
52
77
  def parse_struct(tokens)
53
78
  struct_fields = {}
54
79
  while tokens.size >= 2 and tokens[1] == :colon
55
80
  name = tokens[0]
56
- tokens, type = self.parse(tokens[2..])
81
+ tokens, type = self.parse(tokens[2..tokens.size])
57
82
  struct_fields[name.to_sym] = type
58
83
  end
59
- return tokens[1..], struct_fields
84
+ return tokens[1..tokens.size], struct_fields
60
85
  end
61
86
 
62
87
  def parse(tokens)
63
88
  while tokens.size > 0
64
89
  if tokens[0] == "type"
65
90
  name = tokens[1]
66
- tokens, type = self.parse(tokens[2..])
91
+ tokens, type = self.parse(tokens[2..tokens.size])
67
92
  @definitions[name.to_sym] = type
68
93
  elsif tokens[0] == "map"
69
- raise SchemaParsingException("Map must be followed by a '[' eg. map[string]data") if tokens[1] != :open_brace
70
- tokens, map_from_type = parse(tokens[2..])
71
- raise SchemaParsingException("Map to type must be followed by a ']' eg. map[string]data") if tokens[0] != :close_brace
72
- tokens, map_to_type = parse(tokens[1..])
73
- return tokens, Bare.Map(map_from_type,map_to_type)
94
+ raise SchemaParsingException.new("Map must be followed by a '[' eg. map[string]data") if tokens[1] != :open_brace
95
+ tokens, map_from_type = parse(tokens[2..tokens.size])
96
+ raise SchemaParsingException.new("Map to type must be followed by a ']' eg. map[string]data") if tokens[0] != :close_brace
97
+ tokens, map_to_type = parse(tokens[1..tokens.size])
98
+ return tokens, Bare.Map(map_from_type, map_to_type)
99
+ elsif tokens[0] == "data" && tokens.size > 3 && tokens[1] == :less_than
100
+ raise SchemaParsingException.new("data< must be followed by a number for a fixed sized bare data") unless tokens[2].is_a?(Numeric)
101
+ raise SchemaParsingException.new("data<# must be followed by a >") unless tokens[3] == :greater_than
102
+ return tokens[4..tokens.size], Bare.DataFixedLen(tokens[2])
74
103
  elsif tokens[0] == "enum"
75
104
  name = tokens[1]
76
- raise SchemaParsingException("Enum must be followed by a '{'") if tokens[2] != :open_block
77
- tokens, enum = parse_enum(tokens[3..])
105
+ raise SchemaParsingException.new("Enum must be followed by a '{'") if tokens[2] != :open_block
106
+ tokens, enum = parse_enum(tokens[3..tokens.size])
78
107
  @definitions[name.to_sym] = enum
79
- return tokens, enum
108
+ elsif tokens[0] == "optional"
109
+ raise SchemaParsingException.new("Optional must be followed by a '< TYPE > you are missing the first <'") if tokens[1] != :less_than
110
+ tokens, optional_type = self.parse(tokens[2..tokens.size])
111
+ raise SchemaParsingException.new("Optional must be followed by a '< TYPE >' you are missing the last >") if tokens[0] != :greater_than
112
+ return tokens[1..tokens.size], Bare.Optional(optional_type)
80
113
  elsif tokens[0] == :open_brace
81
- tokens, arr_type = parse(tokens[2..])
82
- return tokens, Bare.Array(arr_type)
114
+ if tokens[1].is_a?(Numeric)
115
+ size = tokens[1]
116
+ raise SchemaParsingException.new("Fixed Length Array size must be followed by a ']'") if tokens[2] != :close_brace
117
+ tokens, arr_type = parse(tokens[3..tokens.size])
118
+ return tokens, Bare.ArrayFixedLen(arr_type, size)
119
+ else
120
+ tokens, arr_type = parse(tokens[2..tokens.size])
121
+ return tokens, Bare.Array(arr_type)
122
+ end
123
+ elsif tokens[0] == :open_paren
124
+ tokens, union_hash = parse_union(tokens[1..tokens.size])
125
+ raise SchemaParsingException.new("Union must be followed by a ')'") if tokens[0] != :close_paren
126
+ return tokens[1..tokens.size], Bare.Union(union_hash)
83
127
  elsif tokens[0] == :open_block
84
- tokens, struct_fields = parse_struct(tokens[1..])
128
+ tokens, struct_fields = parse_struct(tokens[1..tokens.size])
85
129
  strct = Bare.Struct(struct_fields)
86
130
  return tokens, strct
87
131
  elsif @primitives.include?(tokens[0])
88
132
  type = @primitives[tokens[0]]
89
- return tokens[1..], type
133
+ return tokens[1..tokens.size], type
134
+ elsif @definitions.keys.include?(tokens[0].to_sym) # User defined type
135
+ return tokens[1..tokens.size], tokens[0].to_sym
136
+ elsif tokens[0].is_a?(String) && tokens[0][0].upcase == tokens[0][0] # Not yet defined user type
137
+ return tokens[1..tokens.size], tokens[0].to_sym
90
138
  else
91
139
  raise SchemaParsingException.new("Unable to parse token: #{tokens[0]}")
92
140
  end