taipo 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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