taipo 1.3.0 → 1.4.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
  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