taipo 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5ae496b6a69d19aceb9f158a587cb7c2f7d0faa1
4
- data.tar.gz: ec0096547e644b51f8759529a246b273ff5354e8
3
+ metadata.gz: cb7fcacea028acafa7344ccfc04fbc3c66e50c13
4
+ data.tar.gz: 052b196e3ed53b4ade6d95abee2b57a266c12a9f
5
5
  SHA512:
6
- metadata.gz: cb51c506c37aaaca6232488649b8cf59246ea2a3f69e7efb5230b677e1c8d32e276949126202325ebea6c9e8f4d5f652287a47e0a6aa6afd08c23da0f4cd86eb
7
- data.tar.gz: 6c1b5b0bb7512d4829167bb5d8400f204fb114cedf16350a0099f71a43a54139a5908c16f08d9a513f9ce23c6c4bbc80b0f4a29c1cd2289411f048bd295006c6
6
+ metadata.gz: 6cced24c850830e10102f010b0b098ff5ed7ebab426f10cee4b51dcdf1ff29622721e75228b9fc4be9f6fa35b8e4917e14961254a4dbfddb0b8110969aad3b0a
7
+ data.tar.gz: 4e9bd2e042ef2f87808bbdab7c10657d96f75bb969aa725e0b02666e4160a433eb3279b601c232c87f4fce37163b23204d504a31a8307ee0649b7846ff510de4
data/README.md CHANGED
@@ -91,7 +91,7 @@ The information in this README is only meant as an introduction. [More informati
91
91
  The simplest case is to write the name of a class. For example, `'String'`. Inherited class names can also be used.
92
92
 
93
93
  ```ruby
94
- check types, a: 'String', b: 'Numeric'
94
+ check types, a: 'String', b: 'Numeric', c: 'Foo::Bar'
95
95
  ```
96
96
 
97
97
  #### Duck Types
data/lib/taipo.rb CHANGED
@@ -64,90 +64,4 @@ module Taipo
64
64
  def self.alias?
65
65
  @@alias
66
66
  end
67
-
68
- # Check if a string is the name of an instance method
69
- #
70
- # @note All this does is check whether the given string begins with a hash
71
- # symbol.
72
- #
73
- # @param str [String] the string to check
74
- #
75
- # @return [Boolean] the result
76
- #
77
- # @since 1.0.0
78
- # @api private
79
- def self.instance_method?(str)
80
- str[0] == '#'
81
- end
82
-
83
- # Return the type definition for an object
84
- #
85
- # @note This assume that each element returned by Enumerator#each has the same
86
- # number of components.
87
- #
88
- # @param obj [Object] the object
89
- #
90
- # @return [String] a type definition of the object
91
- #
92
- # @since 1.1.0
93
- # @api private
94
- def self.object_to_type_def(obj)
95
- return obj.class.name unless obj.is_a? Enumerable
96
-
97
- if obj.is_a? Array
98
- element_types = Hash.new
99
- obj.each { |o| element_types[self.object_to_type_def(o)] = true }
100
- if element_types.empty?
101
- obj.class.name
102
- else
103
- obj.class.name + '<' + element_types.keys.join('|') + '>'
104
- end
105
- else
106
- element_types = Array.new
107
- obj.each.with_index do |element,index_e|
108
- element.each.with_index do |component,index_c|
109
- element_types[index_c] = Hash.new if index_e == 0
110
- c_type = self.object_to_type_def(component)
111
- element_types[index_c][c_type] = true
112
- end
113
- end
114
- inner = element_types.reduce('') do |memo,e|
115
- e_type = e.keys.join('|')
116
- (memo == '') ? e_type : memo + ',' + e_type
117
- end
118
- if element_types.empty?
119
- obj.class.name
120
- else
121
- obj.class.name + '<' + inner + '>'
122
- end
123
- end
124
- end
125
-
126
- # Check if a string is the name of a symbol
127
- #
128
- # @note All this does is check whether the given string begins with a colon.
129
- #
130
- # @param str [String] the string to check
131
- #
132
- # @return [Boolean] the result
133
- #
134
- # @since 1.2.0
135
- # @api private
136
- def self.symbol?(str)
137
- str[0] == ':'
138
- end
139
-
140
- # Return a String representation of an array of {Taipo::TypeElement}
141
- #
142
- # @param types [Array<Taipo::TypeElement>] the array of {Taipo::TypeElement}
143
- #
144
- # @return [String] the String representation
145
- #
146
- # @since 1.1.0
147
- # @api private
148
- def self.types_to_s(types)
149
- types.reduce('') do |memo,t|
150
- (memo == '') ? t.to_s : memo + '|' + t.to_s
151
- end
152
- end
153
67
  end
data/lib/taipo/cache.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  module Taipo
2
+
2
3
  # A cache of Taipo::TypeElement created from parsed type definitions
3
4
  #
4
5
  # @since 1.0.0
5
6
  # @api private
6
7
  module Cache
7
-
8
+
8
9
  # The hash that acts as the cache
9
10
  #
10
11
  # @since 1.0.0
data/lib/taipo/check.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  require 'taipo/cache'
2
2
  require 'taipo/exceptions'
3
3
  require 'taipo/parser'
4
+ require 'taipo/type_elements'
4
5
  require 'taipo/type_element'
6
+ require 'taipo/utilities'
5
7
 
6
8
  module Taipo
7
9
 
@@ -86,12 +88,12 @@ module Taipo
86
88
  is_match = types.any? { |t| t.match? arg }
87
89
 
88
90
  unless collect_invalids || is_match
89
- if Taipo::instance_method? v
91
+ if Taipo::Utilities.instance_method? v
90
92
  msg = "Object '#{k}' does not respond to #{v}."
91
- elsif Taipo::symbol? v
93
+ elsif Taipo::Utilities.symbol? v
92
94
  msg = "Object '#{k}' is not equal to #{v}."
93
95
  elsif arg.is_a? Enumerable
94
- type_def = Taipo.object_to_type_def arg
96
+ type_def = Taipo::Utilities.object_to_type_def arg
95
97
  msg = "Object '#{k}' is #{type_def} but expected #{v}."
96
98
  else
97
99
  msg = "Object '#{k}' is #{arg.class.name} but expected #{v}."
@@ -125,8 +127,8 @@ module Taipo
125
127
  #
126
128
  # This is the callback called by Ruby when a module is included. In this
127
129
  # case, the callback will alias the method +__types__+ as +types+ if
128
- # {Taipo.alias?} returns true. {Taipo.alias} is reset to true at the end of
129
- # this method.
130
+ # {Taipo.alias?} returns true. {Taipo::@@alias} is reset to true at the end
131
+ # of this method.
130
132
  #
131
133
  # @param extender [Class|Module] the class or module extending this module
132
134
  #
@@ -142,8 +144,8 @@ module Taipo
142
144
  #
143
145
  # This is the callback called by Ruby when a module is included. In this
144
146
  # case, the callback will alias the method +__types__+ as +types+ if
145
- # {Taipo.alias?} returns true. {Taipo.alias} is reset to true at the end of
146
- # this method.
147
+ # {Taipo.alias?} returns true. {Taipo::@@alias} is reset to true at the end
148
+ # of this method.
147
149
  #
148
150
  # @param includer [Class|Module] the class or module including this module
149
151
  #
data/lib/taipo/parser.rb CHANGED
@@ -1,18 +1,27 @@
1
1
  require 'taipo/exceptions'
2
+ require 'taipo/parser/stack'
2
3
  require 'taipo/parser/validater'
4
+ require 'taipo/refinements'
5
+ require 'taipo/type_elements'
3
6
  require 'taipo/type_element'
7
+ require 'taipo/type_element/children'
8
+ require 'taipo/type_element/constraints'
9
+ require 'taipo/type_element/constraint'
4
10
 
5
11
  module Taipo
6
12
 
7
13
  # A parser of Taipo type definitions
14
+ #
8
15
  # @since 1.0.0
9
16
  module Parser
10
17
 
11
- # Return an array of Taipo::TypeElements based on +str+
18
+ using Taipo::Refinements
19
+
20
+ # Return a Taipo::TypeElements object based on +str+
12
21
  #
13
22
  # @param str [String] a type definition
14
23
  #
15
- # @return [Array<Taipo:TypeElement>] the result
24
+ # @return [Taipo:TypeElements] the result
16
25
  #
17
26
  # @raise [::TypeError] if +str+ is not a String
18
27
  # @raise [Taipo::SyntaxError] if +str+ is not a valid type definition
@@ -21,198 +30,328 @@ module Taipo
21
30
  def self.parse(str)
22
31
  Taipo::Parser::Validater.validate str
23
32
 
33
+ stack = Taipo::Parser::Stack.new
34
+ i = 0
35
+ subject = :implied
36
+ chars = str.chars
24
37
  content = ''
25
- previous = ''
26
- is_fallthrough = false
27
- fallthroughs = [ '/', '"' ]
28
- closing_symbol = ''
29
- stack = Array.new
30
- elements = Array.new
31
- stack.push elements
32
38
 
33
- str.each_char do |c|
34
- c = '+' + c if is_fallthrough
39
+ while (i < chars.size)
40
+ reset = true
35
41
 
36
- case c
42
+ case chars[i]
43
+ when ' '
44
+ i += 1
45
+ next
37
46
  when '|'
38
- unless attached? previous # Previous character must have been '>' or ')'.
39
- el = Taipo::TypeElement.new name: content
40
- content = ''
41
- elements = stack.pop
42
- elements.push el
43
- stack.push elements
44
- end
47
+ stack = process_sum stack, name: content
48
+ subject = :implied
45
49
  when '<'
46
- el = Taipo::TypeElement.new name: content
47
- content = ''
48
- stack.push el
49
- child_type = Taipo::TypeElement::ChildType.new
50
- stack.push child_type
51
- first_component = Array.new
52
- stack.push first_component
50
+ stack = process_collection :open, stack, name: content
51
+ subject = :implied
53
52
  when '>'
54
- if attached? previous # Previous character must have been '>' or ')'.
55
- last_component = stack.pop
56
- else
57
- el = Taipo::TypeElement.new name: content.strip
58
- content = ''
59
- last_component = stack.pop
60
- last_component.push el
61
- end
62
- child_type = stack.pop
63
- child_type.push last_component
64
- parent_el = stack.pop
65
- parent_el.child_type = child_type
66
- elements = stack.pop
67
- elements.push parent_el
68
- stack.push elements
53
+ stack = process_collection :close, stack, name: content
54
+ subject = :made
55
+ when ','
56
+ stack = process_component stack, name: content
57
+ subject = :implied
69
58
  when '('
70
- if unattached? previous
71
- el = Taipo::TypeElement.new name: 'Object'
72
- content = ''
73
- elsif attached_collection? previous # Previous character must have been '>'.
74
- elements = stack.pop
75
- el = elements.pop
76
- stack.push elements
77
- else
78
- el = Taipo::TypeElement.new name: content
79
- content = ''
80
- end
81
- stack.push el
82
- cst_collection = Array.new
83
- stack.push cst_collection
84
- when '#'
85
- if unattached? previous
86
- content = '#'
87
- else
88
- cst = Taipo::TypeElement::Constraint.new
89
- content = ''
90
- cst_collection = stack.pop
91
- cst_collection.push cst
92
- stack.push cst_collection
93
- end
94
- when ':'
95
- if unattached? previous
96
- content = ':'
97
- elsif content.strip.empty?
98
- content = ':'
99
- else
100
- content = content
101
- cst = Taipo::TypeElement::Constraint.new name: content.strip
102
- content = ''
103
- cst_collection = stack.pop
104
- cst_collection.push cst
105
- stack.push cst_collection
106
- end
107
- when ',' # We could be inside a collection or a set of constraints
108
- if inside_collection? stack
109
- previous_component = stack.pop
110
- el = Taipo::TypeElement.new name: content.strip
111
- content = ''
112
- previous_component.push el
113
- child_type = stack.pop
114
- child_type.push previous_component
115
- stack.push child_type
116
- next_component = Array.new
117
- stack.push next_component
118
- else
119
- cst_collection = stack.pop
120
- cst = cst_collection.pop
121
- cst.value = content.strip
122
- content = ''
123
- cst_collection.push cst
124
- stack.push cst_collection
125
- end
126
- when ')'
127
- cst_collection = stack.pop
128
- cst = cst_collection.pop
129
- cst.value = content.strip
59
+ stack = process_subject stack, name: content, subject: subject
60
+ stack, i = process_constraints stack, chars: chars, index: i+1
61
+ else
62
+ reset = false
63
+ subject = :unmade
64
+ end
65
+
66
+ content = (reset) ? '' : content + chars[i]
67
+ i += 1
68
+ end
69
+
70
+ stack = process_end stack, name: content
71
+
72
+ stack.result
73
+ end
74
+
75
+ # Check whether the character should be skipped
76
+ #
77
+ # This method determines whether a particular character +c+ should be
78
+ # skipped based on +states+. It also updates +states+.
79
+ #
80
+ # @param c [String] the character to check
81
+ # @param states [Hash<Symbol,Boolean>] a state machine
82
+ #
83
+ # @return [Boolean,Hash<Symbol,Boolean>] the result and the updated state
84
+ # machine
85
+ #
86
+ # @since 1.4.0
87
+ # @api private
88
+ def self.escape?(c, states)
89
+ if states[:esc]
90
+ states[:esc] = false
91
+ return skip, states
92
+ end
93
+
94
+ skip = true
95
+
96
+ case c
97
+ when "'"
98
+ states[:ss] = !states[:ss] unless states[:re] || states[:ds]
99
+ when '"'
100
+ states[:ds] = !states[:ds] unless states[:re] || states[:ss]
101
+ when '/'
102
+ states[:re] = !states[:re] unless states[:ss] || states[:ds]
103
+ when '\\'
104
+ states[:esc] = true
105
+ else
106
+ skip = false
107
+ end
108
+
109
+ return skip, states
110
+ end
111
+
112
+ # Parse the constraint expressed as a string
113
+ #
114
+ # @note If the constraint is in the form of an instance method (eg. #foo)
115
+ # this method uses {Taipo::TypeElement::Constraint::METHOD} as the name
116
+ # returned.
117
+ #
118
+ # @param str [String] the constraint expressed as a string
119
+ #
120
+ # @return [String,String] the name and the value
121
+ #
122
+ # @since 1.4.0
123
+ # @api private
124
+ def self.parse_constraint(str)
125
+ str.strip!
126
+ in_name = nil
127
+ name = ''
128
+ content = ''
129
+ str.each_char do |c|
130
+ if c == '#' && in_name.nil?
131
+ name = Taipo::TypeElement::Constraint::METHOD
132
+ in_name = false
133
+ elsif c == ':' && in_name.nil?
134
+ name = 'val'
135
+ content = content + c
136
+ in_name = false
137
+ elsif c == ':' && in_name
138
+ name = content
130
139
  content = ''
131
- cst_collection.push cst
132
- el = stack.pop
133
- el.constraints = cst_collection
134
- elements = stack.pop
135
- elements.push el
136
- stack.push elements
140
+ in_name = false
137
141
  else
138
- if is_fallthrough
139
- c = c[1]
140
- is_fallthrough = false if c == closing_symbol
141
- elsif fallthroughs.any? { |f| f == c }
142
- is_fallthrough = true
143
- closing_symbol = c
144
- end
145
142
  content = content + c
143
+ in_name = true if in_name.nil?
146
144
  end
147
- previous = c
148
145
  end
146
+ value = content.strip
147
+ return name, value
148
+ end
149
149
 
150
- unless content.empty?
151
- el = Taipo::TypeElement.new name: content
152
- elements = stack.pop
153
- elements.push el
154
- stack.push elements
150
+ # Process a collection
151
+ #
152
+ # This method either adds or updates a collection on +stack+ depending on
153
+ # +direction+. If +direction+ is +:open+, this adds a
154
+ # {Taipo::TypeElement} to +stack+ representing the class of the collection.
155
+ # If +direction+ is +:close+, this adds a {Taipo::TypeElement} to +stack+
156
+ # representing the class of the final component of the collection.
157
+ #
158
+ # @param direction [Symbol] Either +:open+ or +:close+ depending on whether
159
+ # this has been called because the parser reached a +<+ character or a
160
+ # +>+ character
161
+ # @param stack [Taipo::Parser::Stack] the stack
162
+ # @param name [String] the name of the class of the {Taipo::TypeElement} to
163
+ # add to +stack+
164
+ #
165
+ # @return [Taipo::Parser::Stack] the updated stack
166
+ #
167
+ # @since 1.4.0
168
+ # @api private
169
+ def self.process_collection(direction, stack, name:)
170
+ case direction
171
+ when :open
172
+ stack = process_name stack, name: name
173
+ stack.add_children
174
+ when :close
175
+ stack = process_name stack, name: name unless name.empty?
176
+ children = stack.remove_children
177
+ stack.update_element :children=, children
155
178
  end
179
+ end
156
180
 
157
- stack.pop
181
+ # Process a component
182
+ #
183
+ # This method adds a {Taipo::TypeElement} to +stack+ representing a
184
+ # component of a collection.
185
+ #
186
+ # @param stack [Taipo::Parser::Stack] the stack
187
+ # @param name [String] the name of the class of the {Taipo::TypeElement} to
188
+ # add to +stack+
189
+ #
190
+ # @return [Taipo::Parser::Stack] the updated stack
191
+ #
192
+ # @since 1.4.0
193
+ # @api private
194
+ def self.process_component(stack, name:)
195
+ stack = process_name stack, name: name
196
+ stack.add_child
158
197
  end
159
-
160
- # Check whether the current element is 'attached' to anything
198
+
199
+ # Process a constraint
161
200
  #
162
- # This check is performed by checking whether +char+ is the final character
163
- # in a collection or constraint.
201
+ # This method adds a {Taipo::TypeElement::Constraint} to the last element in
202
+ # +stack+.
164
203
  #
165
- # @param char [String] the character to use in the test
204
+ # @param stack [Taipo::Parser::Stack] the stack
205
+ # @param raw [String] the constraint expressed as a string
166
206
  #
167
- # @return [Boolean] the result
207
+ # @return [Taipo::Parser::Stack] the updated stack
168
208
  #
169
- # @since 1.2.0
209
+ # @since 1.4.0
170
210
  # @api private
171
- def self.attached?(char)
172
- char == '>' || char == ')'
211
+ def self.process_constraint(stack, raw:)
212
+ n, v = parse_constraint raw
213
+ stack.add_constraint Taipo::TypeElement::Constraint.new(name: n, value: v)
173
214
  end
174
215
 
175
- # Check whether the current element is 'attached' to a collection
216
+ # Process a series of constraints
176
217
  #
177
- # Like {self.attached?}, this check is performed by checking +char+. In
178
- # this case, the check is whether +char+ is the final character in a
179
- # collection.
218
+ # This method adds a {Taipo::TypeElement::Constraints} to the last element
219
+ # in +stack+. Because it parses +chars+, it also returns an updated +index+.
180
220
  #
181
- # @param char [String] the character to use in the test
221
+ # @param stack [Taipo::Parser::Stack] the stack
222
+ # @param chars [Array<String>] a character array
223
+ # @param index [Integer] the index of +chars+ at which to begin parsing
182
224
  #
183
- # @return [Boolean] the result
225
+ # @return [Taipo::Parser::Stack,Integer] the updated stack and the updated
226
+ # index
184
227
  #
185
- # @since 1.2.0
228
+ # @since 1.4.0
186
229
  # @api private
187
- def self.attached_collection?(char)
188
- char == '>'
230
+ def self.process_constraints(stack, chars:, index:)
231
+ stack.add_constraints
232
+
233
+ inside = { ss: false, ds: false, re: false, esc: false }
234
+ content = ''
235
+
236
+ while (index < chars.size)
237
+ skip, inside = escape?(chars[index], inside)
238
+ if skip
239
+ content = content + chars[index]
240
+ index += 1
241
+ next
242
+ end
243
+
244
+ case chars[index]
245
+ when ')'
246
+ stack = process_constraint stack, raw: content
247
+ break
248
+ when ','
249
+ stack = process_constraint stack, raw: content
250
+ content = ''
251
+ else
252
+ content = content + chars[index]
253
+ end
254
+
255
+ index += 1
256
+ end
257
+
258
+ constraints = stack.remove_constraints
259
+ stack.update_element :constraints=, constraints
260
+
261
+ return stack, index
189
262
  end
190
263
 
191
- # Check if the parser is inside a collection
264
+ # Process the end of the type definition
192
265
  #
193
- # @param stack [Array] the stack of parsed elements
266
+ # The design of {Taipo::Parser.parse} means that at the end of the loop, an
267
+ # element may remain to be added. This method add any remaining element to
268
+ # +stack+.
194
269
  #
195
- # @return [Boolean] the result
270
+ # @param stack [Taipo::Parser::Stack] the stack
271
+ # @param name [String] the name of the class of the {Taipo::TypeElement} to
272
+ # add to +stack+
196
273
  #
197
- # @since 1.0.0
274
+ # @return [Taipo::Parser::Stack] the updated stack
275
+ #
276
+ # @since 1.4.0
198
277
  # @api private
199
- def self.inside_collection?(stack)
200
- stack[-2]&.class == Taipo::TypeElement::ChildType
278
+ def self.process_end(stack, name:)
279
+ return stack if name.empty?
280
+
281
+ process_name stack, name: name
282
+ end
283
+
284
+ # Process the name of a {Taipo::TypeElement}
285
+ #
286
+ # This method adds a {Taipo::TypeElement} to +stack+ with the name +name+.
287
+ #
288
+ # @note Taipo allows certain bare constraints to be written in type
289
+ # definitions. If +name+ is a bare constraint (either an instance method
290
+ # or a symbol), this method adds a {Taipo::TypeElement} representing the
291
+ # Object class with the relevant constraint.
292
+ #
293
+ # @param stack [Taipo::Parser::Stack] the stack
294
+ # @param name [String] the name of the class of the {Taipo::TypeElement} to
295
+ # add to +stack+
296
+ #
297
+ # @return [Taipo::Parser::Stack] the updated stack
298
+ #
299
+ # @since 1.4.0
300
+ # @api private
301
+ def self.process_name(stack, name:)
302
+ if name.bare_constraint?
303
+ chars = "(#{name})".chars
304
+ stack = process_subject stack, name: '', subject: :implied
305
+ stack, i = process_constraints stack, chars: chars, index: 1
306
+ stack
307
+ else
308
+ stack.add_element name: name
309
+ end
201
310
  end
202
311
 
203
- # Check whether the current element is 'unattached' to anything
312
+ # Process the subject of a series of constraints
204
313
  #
205
- # This check checks whether +char+ represents the beginning of a discrete
206
- # type definition.
314
+ # Taipo allows for a type definition to specify a series of constraints
315
+ # that constrain the particular type (the subject). This method adds a
316
+ # {Taipo::TypeElement} to +stack+ depending on the value of +subject+.
207
317
  #
208
- # @param char [String] the character to use in the test
318
+ # @param stack [Taipo::Parser::Stack] the stack
319
+ # @param name [String] the name of the class of the {Taipo::TypeElement} to
320
+ # add to +stack+
321
+ # @param subject [Symbol] whether the subject is :made, :unmade or :implied
209
322
  #
210
- # @return [Boolean] the result
323
+ # @return [Taipo::Parser::Stack] the updated stack
211
324
  #
212
- # @since 1.2.0
325
+ # @since 1.4.0
213
326
  # @api private
214
- def self.unattached?(char)
215
- char.empty? || char == '|' || char == '<'
327
+ def self.process_subject(stack, name:, subject:)
328
+ case subject
329
+ when :made
330
+ stack
331
+ when :unmade
332
+ process_name stack, name: name
333
+ when :implied
334
+ process_name stack, name: 'Object'
335
+ end
336
+ end
337
+
338
+ # Process a sum of types
339
+ #
340
+ # This method adds a {Taipo::TypeElement} to +stack+ representing the former
341
+ # of the types in the sum.
342
+ #
343
+ # @param stack [Taipo::Parser::Stack] the stack
344
+ # @param name [String] the name of the class of the {Taipo::TypeElement} to
345
+ # add to +stack+
346
+ #
347
+ # @return [Taipo::Parser::Stack] the updated stack
348
+ #
349
+ # @since 1.4.0
350
+ # @api private
351
+ def self.process_sum(stack, name:)
352
+ return stack if name.empty?
353
+
354
+ process_name stack, name: name
216
355
  end
217
356
  end
218
357
  end