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.
@@ -0,0 +1,143 @@
1
+ require 'taipo/type_elements'
2
+ require 'taipo/type_element'
3
+ require 'taipo/type_element/children'
4
+ require 'taipo/type_element/constraints'
5
+ require 'taipo/type_element/constraint'
6
+
7
+ module Taipo
8
+ module Parser
9
+
10
+ # A stack of parsed or partially parsed elements
11
+ #
12
+ # @since 1.4.0
13
+ # @api private
14
+ class Stack < Array
15
+
16
+ # Initialize the stack
17
+ #
18
+ # @since 1.4.0
19
+ # @api private
20
+ def initialize
21
+ self.push Taipo::TypeElements.new
22
+ end
23
+
24
+ # Return the resulting {Taipo::TypeElements} object
25
+ #
26
+ # @note This should not be called until parsing is complete.
27
+ #
28
+ # @return [Taipo::TypeElements] the parsed object representing the
29
+ # relevant type definition
30
+ #
31
+ # @raise [RuntimeError] if there is more than one element in the stack
32
+ #
33
+ # @since 1.4.0
34
+ # @api private
35
+ def result
36
+ msg = "Something went wrong. There should only be one element left."
37
+ raise RuntimeError, msg if self.size != 1
38
+ self.first
39
+ end
40
+
41
+ # Add a {Taipo::TypeElement::Children} object to the top of the stack
42
+ #
43
+ # @note Due to the way {Taipo::Parser} is implemented, this method will
44
+ # also add an empty {Taipo::TypeElements} object to the stack. This
45
+ # represents the first (and possibly only) component of the collection.
46
+ #
47
+ # @return [Taipo::Parser::Stack] the updated stack
48
+ #
49
+ # @since 1.4.0
50
+ # @api private
51
+ def add_children
52
+ self.push Taipo::TypeElement::Children.new
53
+ self.push Taipo::TypeElements.new
54
+ end
55
+
56
+ # Remove a {Taipo::TypeElement::Children} object from the top of the stack
57
+ #
58
+ # @note Due to the way {Taipo::Parser} is implemented, this method first
59
+ # removes the child that is at the top of the stack, adds that to the
60
+ # set of children and then returns the children.
61
+ #
62
+ # @return [Taipo::TypeElement::Children] the children
63
+ #
64
+ # @since 1.4.0
65
+ # @api private
66
+ def remove_children
67
+ child = self.pop
68
+ self.last.push child
69
+ self.pop
70
+ end
71
+
72
+ # Add a {Taipo::TypeElements} object to the top of the stack
73
+ #
74
+ # @note Due to the way {Taipo::Parser} is implemented, this method first
75
+ # removes the child that is at the top of the stack, adds that to the
76
+ # set of children and then add a new child.
77
+ #
78
+ # @return [Taipo::Parser::Stack] the updated stack
79
+ #
80
+ # @since 1.4.0
81
+ # @api private
82
+ def add_child
83
+ child = self.pop
84
+ self.last.push child
85
+ self.push Taipo::TypeElements.new
86
+ end
87
+
88
+ # Add a {Taipo::TypeElement::Constraints} object to the top of the stack
89
+ #
90
+ # @return [Taipo::Parser::Stack] the updated stack
91
+ #
92
+ # @since 1.4.0
93
+ # @api private
94
+ def add_constraints
95
+ self.push Taipo::TypeElement::Constraints.new
96
+ end
97
+
98
+ # Remove a {Taipo::TypeElement::Constraints} object from the top of the
99
+ # stack
100
+ #
101
+ # @return [Taipo::TypeElement::Constraints] the constraints
102
+ #
103
+ # @since 1.4.0
104
+ # @api private
105
+ def remove_constraints
106
+ self.pop
107
+ end
108
+
109
+ # Add a {Taipo::TypeElement::Constraint} object to the top of the stack
110
+ #
111
+ # @return [Taipo::Parser::Stack] the updated stack
112
+ #
113
+ # @since 1.4.0
114
+ # @api private
115
+ def add_constraint(constraint)
116
+ self.last.push constraint
117
+ self
118
+ end
119
+
120
+ # Add a {Taipo::TypeElement} object to the top of the stack
121
+ #
122
+ # @return [Taipo::Parser::Stack] the updated stack
123
+ #
124
+ # @since 1.4.0
125
+ # @api private
126
+ def add_element(name:)
127
+ self.last.push Taipo::TypeElement.new(name: name)
128
+ self
129
+ end
130
+
131
+ # Update the {Taipo::TypeElement} object at the top of the stack
132
+ #
133
+ # @return [Taipo::Parser::Stack] the updated stack
134
+ #
135
+ # @since 1.4.0
136
+ # @api private
137
+ def update_element(method, arg)
138
+ self.last.last.send method, arg
139
+ self
140
+ end
141
+ end
142
+ end
143
+ end
@@ -6,14 +6,14 @@ module Taipo
6
6
  # @since 1.0.0
7
7
  # @api private
8
8
  class SyntaxState
9
-
9
+
10
10
  # Initialize a new state machine
11
11
  #
12
12
  # @param state_names [Array<Symbol>] an array of symbols designating the
13
13
  # particular states to be used
14
14
  # @param counter_names_and_closers [Array<Array<Symbol>,Hash<Symbol,
15
15
  # String>>] an array of two collections: an array of symbols
16
- # designating the names the state machine will use for counting
16
+ # designating the names the state machine will use for counting
17
17
  # brackets and a hash of closing characters used for each bracket (the
18
18
  # key for each closing character should have the same name as the name
19
19
  # used for the counter)
@@ -58,10 +58,10 @@ module Taipo
58
58
  @status[key] = :allowed
59
59
  end
60
60
 
61
- # Set all statuses to be +:allowed+ except those specified in the
61
+ # Set all statuses to be +:allowed+ except those specified in the
62
62
  # +except+ array
63
63
  #
64
- # @note Statuses which have been set to +:disabled+ will not be updated
64
+ # @note Statuses which have been set to +:disabled+ will not be updated.
65
65
  #
66
66
  # @param except [Array<Symbol>] keys not to update to +:allowed+ (will
67
67
  # instead be set to +:prohibited+)
@@ -79,7 +79,7 @@ module Taipo
79
79
  # @return [Boolean] the result
80
80
  #
81
81
  # @since 1.0.0
82
- # @api private
82
+ # @api private
83
83
  def allowed?(key)
84
84
  @status[key] == :allowed
85
85
  end
@@ -99,7 +99,7 @@ module Taipo
99
99
  # Decrement the count for the given +key+ by 1
100
100
  #
101
101
  # @param key [Symbol] the key for the counter
102
- #
102
+ #
103
103
  # @since 1.0.0
104
104
  # @api private
105
105
  def decrement(key)
@@ -146,7 +146,7 @@ module Taipo
146
146
  # @return [Boolean] the result
147
147
  #
148
148
  # @since 1.0.0
149
- # @api private
149
+ # @api private
150
150
  def inside?(key)
151
151
  @counter[key] > 0
152
152
  end
@@ -177,7 +177,7 @@ module Taipo
177
177
  # Set all statuses to be +:prohibited+ except those specified in the
178
178
  # +except+ array
179
179
  #
180
- # @note Statuses which have been set to +:disabled+ will not be updated
180
+ # @note Statuses which have been set to +:disabled+ will not be updated.
181
181
  #
182
182
  # @param except [Array<Symbol>] keys not to update to +:prohibited+ (will
183
183
  # instead be set to +:allowed+)
@@ -201,11 +201,12 @@ module Taipo
201
201
  # Set all statuses to be +status+ except those specified in the +except+
202
202
  # array
203
203
  #
204
- # @note Statuses which have been set to +:disabled+ will not be updated
204
+ # @note Statuses which have been set to +:disabled+ will not be updated.
205
205
  #
206
206
  # @param status [Symbol] the value for all statuses
207
207
  # @param except [Hash<Array<Symbol>, Symbol>] the exceptions
208
- # @option except [Array<Symbol>] :exceptions keys not to update to +status+
208
+ # @option except [Array<Symbol>] :exceptions keys not to update to
209
+ # +status+
209
210
  # @option except [Symbol] :status the value for exceptions
210
211
  #
211
212
  # @since 1.0.0
@@ -223,7 +224,7 @@ module Taipo
223
224
  # brackets
224
225
  #
225
226
  # @since 1.0.0
226
- # @api
227
+ # @api
227
228
  def unbalanced()
228
229
  @counter.reduce(Array.new) do |memo, c|
229
230
  (c[1] == 0) ? memo : memo.push(@closers[c[0]])
@@ -11,9 +11,10 @@ module Taipo
11
11
  #
12
12
  # === Names
13
13
  #
14
- # 'String', 'Numeric'
14
+ # 'String', 'Numeric', 'Foo::Bar'
15
15
  #
16
- # A name should be the name of a class or a module.
16
+ # A name should be the name of a class or a module. A name can include a
17
+ # namespace.
17
18
  #
18
19
  # The validater does not check whether the name represents a valid name in
19
20
  # the current context nor does it check whether the name complies with
@@ -120,8 +121,8 @@ module Taipo
120
121
  # ':foo|:bar', ':one|:two|:three'
121
122
  #
122
123
  # It's possible to approximate the enum idiom available in many languages
123
- # by creating a sum type consisting of Symbols. As a convenience, Taipo
124
- # parses these values as constraints on the Object class. In other words,
124
+ # by creating a sum type consisting of Symbols. As a convenience, Taipo
125
+ # parses these values as constraints on the Object class. In other words,
125
126
  # +':foo|:bar'+ is really +'Object(val: :foo)|Object(val: :bar)'+.
126
127
  #
127
128
  # @since 1.0.0
@@ -143,92 +144,159 @@ module Taipo
143
144
  msg = "The string to be checked was empty."
144
145
  raise Taipo::SyntaxError, msg if str.empty?
145
146
 
146
- status_array = [ :bar, :lab, :rab, :lpr, :rpr, :hsh, :cln, :sls, :qut,
147
- :cma, :spc, :oth, :end ]
148
- counter_array = [ [ :angle, :paren, :const ],
149
- { angle: '>', paren: ')', const: ":' or '#" } ]
147
+ status_array = [ :bar, :lab, :rab, :lpr, :hsh, :cln, :cma, :spc_bar,
148
+ :spc_rab, :spc_rpr, :spc_cma, :spc_oth, :mth, :sym,
149
+ :nme, :end ]
150
+ counter_array = [ [ :angle ], { angle: '>' } ]
151
+
150
152
  state = Taipo::Parser::SyntaxState.new(status_array, counter_array)
153
+ state.prohibit_all except: [ :lpr, :hsh, :cln, :nme ]
151
154
 
152
155
  i = 0
153
156
  chars = str.chars
154
- str_length = chars.size
155
-
156
- state.prohibit_all except: [ :lpr, :hsh, :cln, :oth ]
157
157
 
158
- while (i < str_length)
158
+ while (i < chars.size)
159
159
  msg = "The string '#{str}' has an error here: #{str[0, i+1]}"
160
160
  case chars[i]
161
+ when ')', '/', '"'
162
+ raise Taipo::SyntaxError, msg
161
163
  when '|' # bar
162
164
  conditions = [ state.allowed?(:bar) ]
163
165
  raise Taipo::SyntaxError, msg unless conditions.all?
164
- state.enable :lab
165
- state.enable :lpr
166
- state.prohibit_all except: [ :lpr, :hsh, :cln, :oth ]
166
+ state.prohibit_all except: [ :lpr, :hsh, :cln, :spc_bar, :nme ]
167
167
  when '<' # lab
168
168
  conditions = [ state.allowed?(:lab) ]
169
169
  raise Taipo::SyntaxError, msg unless conditions.all?
170
- state.prohibit_all except: [ :lpr, :hsh, :cln, :oth ]
170
+ state.prohibit_all except: [ :lpr, :hsh, :cln, :nme ]
171
171
  state.increment :angle
172
172
  when '>' # rab
173
173
  conditions = [ state.allowed?(:rab), state.inside?(:angle) ]
174
174
  raise Taipo::SyntaxError, msg unless conditions.all?
175
- state.prohibit_all except: [ :bar, :rab, :lpr, :end ]
175
+ state.prohibit_all except: [ :bar, :rab, :lpr, :spc_rab, :end ]
176
176
  state.decrement :angle
177
177
  when '(' # lpr
178
- conditions = [ state.allowed?(:lpr), state.outside?(:paren) ]
178
+ conditions = [ state.allowed?(:lpr) ]
179
179
  raise Taipo::SyntaxError, msg unless conditions.all?
180
- state.prohibit_all except: [ :hsh, :oth ]
181
- state.increment :paren
182
- state.increment :const
183
- when ')' # rpr
184
- conditions = [ state.allowed?(:rpr), state.inside?(:paren) ]
185
- raise Taipo::SyntaxError, msg unless conditions.all?
186
- state.prohibit_all except: [ :bar, :rab, :end ]
187
- state.decrement :paren
180
+ i = Taipo::Parser::Validater.validate_constraints(str, start: i+1)
181
+ state.prohibit_all except: [ :bar, :rab, :spc_rpr, :end ]
188
182
  when '#' # hsh
189
183
  conditions = [ state.allowed?(:hsh) ]
190
184
  raise Taipo::SyntaxError, msg unless conditions.all?
191
- if state.outside? :paren
192
- state.disable :lab
193
- state.disable :lpr
194
- state.prohibit_all except: [ :oth ]
185
+ state.prohibit_all except: [ :mth ]
186
+ when ':' # cln
187
+ if chars[i+1] == ':' && chars[i+2] != ':'
188
+ conditions = [ state.allowed?(:nme) ]
189
+ raise Taipo::SyntaxError, msg unless conditions.all?
190
+ state.prohibit_all except: [ :nme ]
191
+ i = i + 1
195
192
  else
196
- state.prohibit_all except: [ :oth ]
197
- state.decrement :const
193
+ conditions = [ state.allowed?(:cln) ]
194
+ raise Taipo::SyntaxError, msg unless conditions.all?
195
+ state.prohibit_all except: [ :sym ]
196
+ end
197
+ when ',' # cma
198
+ conditions = [ state.allowed?(:cma), state.inside?(:angle)]
199
+ raise Taipo::SyntaxError, msg unless conditions.all?
200
+ state.prohibit_all except: [ :hsh, :cln, :spc_cma, :nme ]
201
+ when ' ' # spc
202
+ conditions = [ state.allowed?(:spc_bar), state.allowed?(:spc_cma),
203
+ state.allowed?(:spc_oth) ]
204
+ raise Taipo::SyntaxError, msg unless conditions.any?
205
+ if state.allowed?(:spc_bar) || state.allowed?(:spc_cma)
206
+ state.prohibit_all except: [ :hsh, :cln, :nme ]
207
+ elsif state.allowed?(:spc_rab) || state.allowed?(:spc_rpr)
208
+ state.prohibit_all except: [ :bar, :hsh, :cln, :nme ]
209
+ elsif state.allowed?(:spc_oth)
210
+ state.prohibit_all except: [ :bar ]
211
+ end
212
+ else # oth
213
+ conditions = [ state.allowed?(:mth), state.allowed?(:sym),
214
+ state.allowed?(:nme) ]
215
+ raise Taipo::SyntaxError, msg unless conditions.any?
216
+ if state.allowed?(:mth)
217
+ state.prohibit_all except: [ :bar, :rab, :cma, :spc_oth, :mth,
218
+ :end ]
219
+ elsif state.allowed?(:sym)
220
+ state.prohibit_all except: [ :bar, :rab, :cma, :spc_oth, :sym,
221
+ :end ]
222
+ elsif state.allowed?(:nme)
223
+ state.prohibit_all except: [ :bar, :lab, :rab, :lpr, :cma,
224
+ :spc_oth, :nme, :end ]
198
225
  end
226
+ end
227
+ i += 1
228
+ end
229
+
230
+ msg_end = "The string '#{str}' ends with an illegal character."
231
+ raise Taipo::SyntaxError, msg_end unless state.allowed?(:end)
232
+
233
+ missing = state.unbalanced
234
+ msg_bal = "The string '#{str}' is missing a '#{missing.first}'."
235
+ raise Taipo::SyntaxError, msg_bal unless missing.size == 0
236
+ end
237
+
238
+ # Check +str+ is a valid set of constraints
239
+ #
240
+ # @param str [String] the type definition
241
+ # @param start [Integer] the index within the type definition where this
242
+ # set of constraints begins
243
+ #
244
+ # @return [Integer] the index within the type definition where this set of
245
+ # set of constraints end
246
+ #
247
+ # @raise [Taipo::SyntaxError] if +str+ is not a valid set of constraints
248
+ #
249
+ # @since 1.4.0
250
+ # @api private
251
+ def self.validate_constraints(str, start: 0)
252
+ status_array = [ :rpr, :hsh, :cln, :sls, :qut, :cma, :spc, :oth ]
253
+ counter_array = [ [ :const ], { const: ":' or '#" } ]
254
+
255
+ state = SyntaxState.new(status_array, counter_array)
256
+ state.prohibit_all except: [ :hsh, :oth ]
257
+ state.increment(:const)
258
+
259
+ i = start
260
+ chars = str.chars
261
+
262
+ while (i < chars.size)
263
+ msg = "The string '#{str}' has an error here: #{str[0, i+1]}"
264
+ case chars[i]
265
+ when '|', '<', '>', '('
266
+ raise Taipo::SyntaxError, msg
267
+ when ')' # rpr
268
+ conditions = [ state.allowed?(:rpr) ]
269
+ raise Taipo::SyntaxError, msg unless conditions.all?
270
+ break # The constraints have ended.
271
+ when '#' # hsh
272
+ conditions = [ state.allowed?(:hsh) ]
273
+ raise Taipo::SyntaxError, msg unless conditions.all?
274
+ state.prohibit_all except: [ :oth ]
275
+ state.decrement :const
199
276
  when ':' # cln
200
277
  conditions = [ state.allowed?(:cln) ]
201
278
  raise Taipo::SyntaxError, msg unless conditions.all?
202
- if state.outside? :paren
203
- state.disable :lab
204
- state.disable :lpr
205
- state.prohibit_all except: [ :oth ]
279
+ if state.count(:const) == 0 # This is a symbol.
280
+ state.prohibit_all except: [ :qut, :oth ]
206
281
  else
207
- if state.count(:const) == 0 # This is a symbol.
208
- state.prohibit_all except: [ :qut, :oth ]
209
- else
210
- state.prohibit_all except: [ :cln, :sls, :qut, :spc, :oth ]
211
- state.decrement :const
212
- end
282
+ state.prohibit_all except: [ :cln, :sls, :qut, :spc, :oth ]
283
+ state.decrement :const
213
284
  end
214
285
  when '/' #sls
215
- conditions = [ state.allowed?(:sls), state.inside?(:paren),
216
- state.outside?(:const) ]
286
+ conditions = [ state.allowed?(:sls), state.outside?(:const) ]
217
287
  raise Taipo::SyntaxError, msg unless conditions.all?
218
288
  i = Taipo::Parser::Validater.validate_regex(str, start: i+1)
219
289
  state.prohibit_all except: [ :rpr, :cma ]
220
290
  when '"' #qut
221
- conditions = [ state.allowed?(:qut), state.inside?(:paren),
222
- state.outside?(:const) ]
291
+ conditions = [ state.allowed?(:qut), state.outside?(:const) ]
223
292
  raise Taipo::SyntaxError, msg unless conditions.all?
224
293
  i = Taipo::Parser::Validater.validate_string(str, start: i+1)
225
294
  state.prohibit_all except: [ :rpr, :cma ]
226
295
  when ',' # cma
227
- conditions = [ state.allowed?(:cma),
228
- state.inside?(:angle) || state.inside?(:paren) ]
296
+ conditions = [ state.allowed?(:cma) ]
229
297
  raise Taipo::SyntaxError, msg unless conditions.all?
230
298
  state.prohibit_all except: [ :spc, :oth ]
231
- state.increment :const if state.inside?(:paren)
299
+ state.increment :const
232
300
  when ' ' # spc
233
301
  conditions = [ state.allowed?(:spc) ]
234
302
  raise Taipo::SyntaxError, msg unless conditions.all?
@@ -236,25 +304,24 @@ module Taipo
236
304
  else # oth
237
305
  conditions = [ state.allowed?(:oth) ]
238
306
  raise Taipo::SyntaxError, msg unless conditions.all?
239
- if state.inside? :paren
240
- state.allow_all except: [ :hsh, :spc ]
241
- else
242
- state.allow_all except: [ :hsh, :cln, :spc ]
243
- end
307
+ state.allow_all except: [ :hsh, :spc ]
244
308
  end
245
309
  i += 1
246
310
  end
247
- msg_end = "The string '#{str}' ends with an illegal character."
248
- raise Taipo::SyntaxError, msg_end unless state.allowed?(:end)
311
+
312
+ msg = "The string '#{str}' is missing a ')'."
313
+ raise Taipo::SyntaxError, msg if i == chars.size
249
314
 
250
315
  missing = state.unbalanced
251
316
  msg_bal = "The string '#{str}' is missing a '#{missing.first}'."
252
317
  raise Taipo::SyntaxError, msg_bal unless missing.size == 0
318
+
319
+ i
253
320
  end
254
321
 
255
322
  # Check +str+ is a valid regular expression
256
323
  #
257
- # @param str [String] a regular expression delimited by '/'
324
+ # @param str [String] the type definition
258
325
  # @param start [Integer] the index within the type definition where this
259
326
  # regex begins
260
327
  #
@@ -308,12 +375,13 @@ module Taipo
308
375
 
309
376
  # Check +str+ is a valid string
310
377
  #
311
- # @param str [String] a string delimited by '"'
378
+ # @param str [String] the type definition
312
379
  # @param start [Integer] the index within the type definition where this
313
380
  # string begins
314
381
  #
315
382
  # @return [Integer] the index within the type definition where this
316
383
  # string ends
384
+ #
317
385
  # @raise [Taipo::SyntaxError] if +str+ is not a valid string
318
386
  #
319
387
  # @since 1.0.0