machete 0.2.1 → 0.3.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.
data/lib/machete/parser.y CHANGED
@@ -1,10 +1,13 @@
1
1
  class Machete::Parser
2
2
 
3
+ token INTEGER
4
+ token STRING
5
+ token ANY
6
+ token EVEN
7
+ token ODD
3
8
  token METHOD_NAME
4
9
  token CLASS_NAME
5
10
  token SYMBOL
6
- token INTEGER
7
- token STRING
8
11
 
9
12
  start expression
10
13
 
@@ -20,7 +23,9 @@ expression : primary
20
23
  }
21
24
 
22
25
  primary : node
26
+ | array
23
27
  | literal
28
+ | any
24
29
 
25
30
  node : CLASS_NAME {
26
31
  result = NodeMatcher.new(val[0].to_sym)
@@ -33,55 +38,65 @@ attrs : attr
33
38
  | attrs "," attr { result = val[0].merge(val[2]) }
34
39
 
35
40
  attr : method_name "=" expression { result = { val[0].to_sym => val[2] } }
41
+ | method_name "^=" STRING {
42
+ result = {
43
+ val[0].to_sym => StartsWithMatcher.new(string_value(val[2]))
44
+ }
45
+ }
46
+ | method_name "$=" STRING {
47
+ result = {
48
+ val[0].to_sym => EndsWithMatcher.new(string_value(val[2]))
49
+ }
50
+ }
36
51
 
37
- # Hack to overcome the fact that "<", ">" and "|" will lex as simple tokens, not
38
- # METHOD_NAME tokens.
52
+ # Hack to overcome the fact that some tokens will lex as simple tokens, not
53
+ # METHOD_NAME tokens, and that "reserved words" will lex as separate kinds of
54
+ # tokens.
39
55
  method_name : METHOD_NAME
56
+ | ANY
57
+ | EVEN
58
+ | ODD
59
+ | "*"
60
+ | "+"
40
61
  | "<"
41
62
  | ">"
63
+ | "^"
42
64
  | "|"
43
65
 
66
+ array : "[" items_opt "]" { result = ArrayMatcher.new(val[1]) }
67
+
68
+ items_opt : /* empty */ { result = [] }
69
+ | items
70
+
71
+ items : item { result = [val[0]] }
72
+ | items "," item { result = val[0] << val[2] }
73
+
74
+ item : expression
75
+ | expression quantifier { result = Quantifier.new(val[0], *val[1]) }
76
+
77
+ quantifier : "*" { result = [0, nil, 1] }
78
+ | "+" { result = [1, nil, 1] }
79
+ | "?" { result = [0, 1, 1] }
80
+ | "{" INTEGER "}" {
81
+ result = [integer_value(val[1]), integer_value(val[1]), 1]
82
+ }
83
+ | "{" INTEGER "," "}" {
84
+ result = [integer_value(val[1]), nil, 1]
85
+ }
86
+ | "{" "," INTEGER "}" {
87
+ result = [0, integer_value(val[2]), 1]
88
+ }
89
+ | "{" INTEGER "," INTEGER "}" {
90
+ result = [integer_value(val[1]), integer_value(val[3]), 1]
91
+ }
92
+ | "{" EVEN "}" { result = [0, nil, 2] }
93
+ | "{" ODD "}" { result = [1, nil, 2] }
94
+
44
95
  literal : SYMBOL { result = LiteralMatcher.new(val[0][1..-1].to_sym) }
45
- | INTEGER {
46
- value = if val[0] =~ /^0[bB]/
47
- val[0][2..-1].to_i(2)
48
- elsif val[0] =~ /^0[oO]/
49
- val[0][2..-1].to_i(8)
50
- elsif val[0] =~ /^0[dD]/
51
- val[0][2..-1].to_i(10)
52
- elsif val[0] =~ /^0[xX]/
53
- val[0][2..-1].to_i(16)
54
- elsif val[0] =~ /^0/
55
- val[0][1..-1].to_i(8)
56
- else
57
- val[0].to_i
58
- end
59
- result = LiteralMatcher.new(value)
60
- }
61
- | STRING {
62
- quote = val[0][0..0]
63
- value = if quote == "'"
64
- val[0][1..-2].gsub("\\\\", "\\").gsub("\\'", "'")
65
- elsif quote == '"'
66
- val[0][1..-2].
67
- gsub("\\\\", "\\").
68
- gsub('\\"', '"').
69
- gsub("\\n", "\n").
70
- gsub("\\t", "\t").
71
- gsub("\\r", "\r").
72
- gsub("\\f", "\f").
73
- gsub("\\v", "\v").
74
- gsub("\\a", "\a").
75
- gsub("\\e", "\e").
76
- gsub("\\b", "\b").
77
- gsub("\\s", "\s").
78
- gsub(/\\([0-7]{1,3})/) { $1.to_i(8).chr }.
79
- gsub(/\\x([0-9a-fA-F]{1,2})/) { $1.to_i(16).chr }
80
- else
81
- raise "Unknown quote: #{quote.inspect}."
82
- end
83
- result = LiteralMatcher.new(value)
84
- }
96
+ | INTEGER { result = LiteralMatcher.new(integer_value(val[0])) }
97
+ | STRING { result = LiteralMatcher.new(string_value(val[0])) }
98
+
99
+ any : ANY { result = AnyMatcher.new }
85
100
 
86
101
  ---- inner
87
102
 
@@ -98,7 +113,65 @@ end
98
113
 
99
114
  private
100
115
 
101
- SIMPLE_TOKENS = ["|", "<", ">", ",", "="]
116
+ def integer_value(value)
117
+ if value =~ /^0[bB]/
118
+ value[2..-1].to_i(2)
119
+ elsif value =~ /^0[oO]/
120
+ value[2..-1].to_i(8)
121
+ elsif value =~ /^0[dD]/
122
+ value[2..-1].to_i(10)
123
+ elsif value =~ /^0[xX]/
124
+ value[2..-1].to_i(16)
125
+ elsif value =~ /^0/
126
+ value[1..-1].to_i(8)
127
+ else
128
+ value.to_i
129
+ end
130
+ end
131
+
132
+ def string_value(value)
133
+ quote = value[0..0]
134
+ if quote == "'"
135
+ value[1..-2].gsub("\\\\", "\\").gsub("\\'", "'")
136
+ elsif quote == '"'
137
+ value[1..-2].
138
+ gsub("\\\\", "\\").
139
+ gsub('\\"', '"').
140
+ gsub("\\n", "\n").
141
+ gsub("\\t", "\t").
142
+ gsub("\\r", "\r").
143
+ gsub("\\f", "\f").
144
+ gsub("\\v", "\v").
145
+ gsub("\\a", "\a").
146
+ gsub("\\e", "\e").
147
+ gsub("\\b", "\b").
148
+ gsub("\\s", "\s").
149
+ gsub(/\\([0-7]{1,3})/) { $1.to_i(8).chr }.
150
+ gsub(/\\x([0-9a-fA-F]{1,2})/) { $1.to_i(16).chr }
151
+ else
152
+ raise "Unknown quote: #{quote.inspect}."
153
+ end
154
+ end
155
+
156
+ # "^" needs to be here because if it were among operators recognized by
157
+ # METHOD_NAME, "^=" would be recognized as two tokens.
158
+ SIMPLE_TOKENS = [
159
+ "|",
160
+ "<",
161
+ ">",
162
+ ",",
163
+ "=",
164
+ "^=",
165
+ "^",
166
+ "$=",
167
+ "[",
168
+ "]",
169
+ "*",
170
+ "+",
171
+ "?",
172
+ "{",
173
+ "}"
174
+ ]
102
175
 
103
176
  COMPLEX_TOKENS = [
104
177
  # INTEGER needs to be before METHOD_NAME, otherwise e.g. "+1" would be
@@ -151,9 +224,14 @@ COMPLEX_TOKENS = [
151
224
  )
152
225
  /x
153
226
  ],
154
- # We exclude "<", ">" and "|" from method names since they are lexed as simple
155
- # tokens. This is because they have also other meanings in Machette patterns
156
- # beside Ruby method names.
227
+ # ANY, EVEN and ODD need to be before METHOD_NAME, otherwise they would be
228
+ # recognized as method names.
229
+ [:ANY, /^any/],
230
+ [:EVEN, /^even/],
231
+ [:ODD, /^odd/],
232
+ # We exclude "*", "+", "<", ">", "^" and "|" from method names since they are
233
+ # lexed as simple tokens. This is because they have also other meanings in
234
+ # Machette patterns beside Ruby method names.
157
235
  [
158
236
  :METHOD_NAME,
159
237
  /^
@@ -162,7 +240,7 @@ COMPLEX_TOKENS = [
162
240
  [a-z_][a-zA-Z0-9_]*[?!=]?
163
241
  |
164
242
  # operator (sorted by length, then alphabetically)
165
- (<=>|===|\[\]=|\*\*|\+@|-@|<<|<=|==|=~|>=|>>|\[\]|[%&*+\-\/^`~])
243
+ (<=>|===|\[\]=|\*\*|\+@|-@|<<|<=|==|=~|>=|>>|\[\]|[%&\-\/`~])
166
244
  )
167
245
  /x
168
246
  ],
@@ -1,6 +1,54 @@
1
1
  require "spec_helper"
2
2
 
3
3
  module Machete::Matchers
4
+ describe Quantifier do
5
+ before :each do
6
+ @quantifier = Quantifier.new(LiteralMatcher.new(42), 0, 100, 10)
7
+ end
8
+
9
+ describe "initialize" do
10
+ it "sets attributes correctly" do
11
+ @quantifier.matcher.should == LiteralMatcher.new(42)
12
+ @quantifier.min.should == 0
13
+ @quantifier.max.should == 100
14
+ @quantifier.step.should == 10
15
+ end
16
+ end
17
+
18
+ describe "==" do
19
+ it "returns true when passed the same object" do
20
+ @quantifier.should == @quantifier
21
+ end
22
+
23
+ it "returns true when passed a Quantifier initialized with the same parameters" do
24
+ @quantifier.should == Quantifier.new(LiteralMatcher.new(42), 0, 100, 10)
25
+ end
26
+
27
+ it "returns false when passed some random object" do
28
+ @quantifier.should_not == Object.new
29
+ end
30
+
31
+ it "returns false when passed a subclass of Quantifier initialized with the same parameters" do
32
+ class SubclassedQuantifier < Quantifier
33
+ end
34
+
35
+ @quantifier.should_not ==
36
+ SubclassedQuantifier.new(LiteralMatcher.new(42), 0, 100, 10)
37
+ end
38
+
39
+ it "returns false when passed a Quantifier initialized with different parameters" do
40
+ @quantifier.should_not ==
41
+ Quantifier.new(LiteralMatcher.new(43), 0, 100, 10)
42
+ @quantifier.should_not ==
43
+ Quantifier.new(LiteralMatcher.new(42), 1, 100, 10)
44
+ @quantifier.should_not ==
45
+ Quantifier.new(LiteralMatcher.new(42), 0, 101, 10)
46
+ @quantifier.should_not ==
47
+ Quantifier.new(LiteralMatcher.new(42), 0, 100, 11)
48
+ end
49
+ end
50
+ end
51
+
4
52
  describe ChoiceMatcher do
5
53
  before :each do
6
54
  @alternatives = [
@@ -137,6 +185,183 @@ module Machete::Matchers
137
185
  end
138
186
  end
139
187
 
188
+ describe ArrayMatcher do
189
+ before :each do
190
+ @items = [
191
+ LiteralMatcher.new(42),
192
+ LiteralMatcher.new(43),
193
+ LiteralMatcher.new(44)
194
+ ]
195
+ @matcher = ArrayMatcher.new(@items)
196
+ end
197
+
198
+ describe "initialize" do
199
+ it "sets attributes correctly" do
200
+ @matcher.items.should == @items
201
+ end
202
+ end
203
+
204
+ describe "==" do
205
+ it "returns true when passed the same object" do
206
+ @matcher.should == @matcher
207
+ end
208
+
209
+ it "returns true when passed a ArrayMatcher initialized with the same parameters" do
210
+ @matcher.should == ArrayMatcher.new(@items)
211
+ end
212
+
213
+ it "returns false when passed some random object" do
214
+ @matcher.should_not == Object.new
215
+ end
216
+
217
+ it "returns false when passed a subclass of ArrayMatcher initialized with the same parameters" do
218
+ class SubclassedArrayMatcher < ArrayMatcher
219
+ end
220
+
221
+ @matcher.should_not == SubclassedArrayMatcher.new(@items)
222
+ end
223
+
224
+ it "returns false when passed a ArrayMatcher initialized with different parameters" do
225
+ @matcher.should_not == ArrayMatcher.new([
226
+ LiteralMatcher.new(45),
227
+ LiteralMatcher.new(46),
228
+ LiteralMatcher.new(47)
229
+ ])
230
+ end
231
+ end
232
+
233
+ describe "matches?" do
234
+ it "works on matcher with no items" do
235
+ matcher = ArrayMatcher.new([])
236
+
237
+ matcher.matches?([]).should be_true
238
+ matcher.matches?([42]).should be_false
239
+ end
240
+
241
+ it "works on matcher with one item" do
242
+ matcher = ArrayMatcher.new([LiteralMatcher.new(42)])
243
+
244
+ matcher.matches?([]).should be_false
245
+ matcher.matches?([42]).should be_true
246
+ matcher.matches?([43]).should be_false
247
+ matcher.matches?([42, 43]).should be_false
248
+ end
249
+
250
+ it "works on matcher with many items" do
251
+ matcher = ArrayMatcher.new([
252
+ LiteralMatcher.new(42),
253
+ LiteralMatcher.new(43),
254
+ LiteralMatcher.new(44)
255
+ ])
256
+
257
+ matcher.matches?([42, 43]).should be_false
258
+ matcher.matches?([42, 43, 44]).should be_true
259
+ matcher.matches?([43, 43, 44]).should be_false
260
+ matcher.matches?([42, 44, 44]).should be_false
261
+ matcher.matches?([42, 43, 45]).should be_false
262
+ matcher.matches?([42, 43, 44, 45]).should be_false
263
+ end
264
+
265
+ it "works on matcher with a bound quantifier" do
266
+ matcher = ArrayMatcher.new([
267
+ Quantifier.new(LiteralMatcher.new(42), 1, 2, 1)
268
+ ])
269
+
270
+ matcher.matches?([]).should be_false
271
+ matcher.matches?([42]).should be_true
272
+ matcher.matches?([43]).should be_false
273
+ matcher.matches?([42, 42]).should be_true
274
+ matcher.matches?([43, 42]).should be_false
275
+ matcher.matches?([42, 43]).should be_false
276
+ matcher.matches?([42, 42, 42]).should be_false
277
+ end
278
+
279
+ it "works on matcher with a bound quantifier with a bigger step" do
280
+ matcher = ArrayMatcher.new([
281
+ Quantifier.new(LiteralMatcher.new(42), 1, 3, 2)
282
+ ])
283
+
284
+ matcher.matches?([]).should be_false
285
+ matcher.matches?([42]).should be_true
286
+ matcher.matches?([43]).should be_false
287
+ matcher.matches?([42, 42]).should be_false
288
+ matcher.matches?([42, 42, 42]).should be_true
289
+ matcher.matches?([43, 42, 42]).should be_false
290
+ matcher.matches?([42, 43, 42]).should be_false
291
+ matcher.matches?([42, 42, 43]).should be_false
292
+ matcher.matches?([42, 42, 42, 42]).should be_false
293
+ end
294
+
295
+ it "works on matcher with a bound quantifier and some items" do
296
+ matcher = ArrayMatcher.new([
297
+ LiteralMatcher.new(42),
298
+ Quantifier.new(LiteralMatcher.new(43), 0, 1, 1),
299
+ LiteralMatcher.new(44)
300
+ ])
301
+
302
+ matcher.matches?([42, 44]).should be_true
303
+ matcher.matches?([42, 43, 44]).should be_true
304
+ matcher.matches?([42, 44, 44]).should be_false
305
+ matcher.matches?([42, 43, 43, 44]).should be_false
306
+ end
307
+
308
+ it "works on matcher with an unbound quantifier" do
309
+ matcher = ArrayMatcher.new([
310
+ Quantifier.new(LiteralMatcher.new(42), 1, nil, 1)
311
+ ])
312
+
313
+ matcher.matches?([]).should be_false
314
+ matcher.matches?([42]).should be_true
315
+ matcher.matches?([43]).should be_false
316
+ matcher.matches?([42, 42]).should be_true
317
+ matcher.matches?([43, 42]).should be_false
318
+ matcher.matches?([42, 43]).should be_false
319
+ matcher.matches?([42, 42, 42]).should be_true
320
+ matcher.matches?([43, 42, 42]).should be_false
321
+ matcher.matches?([42, 43, 42]).should be_false
322
+ matcher.matches?([42, 42, 43]).should be_false
323
+ end
324
+
325
+ it "works on matcher with an unbound quantifier with a bigger step" do
326
+ matcher = ArrayMatcher.new([
327
+ Quantifier.new(LiteralMatcher.new(42), 1, nil, 2)
328
+ ])
329
+
330
+ matcher.matches?([]).should be_false
331
+ matcher.matches?([42]).should be_true
332
+ matcher.matches?([43]).should be_false
333
+ matcher.matches?([42, 42]).should be_false
334
+ matcher.matches?([42, 42, 42]).should be_true
335
+ matcher.matches?([43, 42, 42]).should be_false
336
+ matcher.matches?([42, 43, 42]).should be_false
337
+ matcher.matches?([42, 42, 43]).should be_false
338
+ end
339
+
340
+ it "works on matcher with an unbound quantifier and some items" do
341
+ matcher = ArrayMatcher.new([
342
+ LiteralMatcher.new(42),
343
+ Quantifier.new(LiteralMatcher.new(43), 0, nil, 1),
344
+ LiteralMatcher.new(44)
345
+ ])
346
+
347
+ matcher.matches?([42, 44]).should be_true
348
+ matcher.matches?([42, 43, 44]).should be_true
349
+ matcher.matches?([42, 44, 44]).should be_false
350
+ matcher.matches?([42, 43, 43, 44]).should be_true
351
+ matcher.matches?([42, 44, 43, 44]).should be_false
352
+ matcher.matches?([42, 43, 44, 44]).should be_false
353
+ matcher.matches?([42, 43, 43, 43, 44]).should be_true
354
+ matcher.matches?([42, 44, 43, 43, 44]).should be_false
355
+ matcher.matches?([42, 43, 44, 43, 44]).should be_false
356
+ matcher.matches?([42, 43, 43, 44, 44]).should be_false
357
+ end
358
+
359
+ it "does not match some random object" do
360
+ @matcher.matches?(Object.new).should be_false
361
+ end
362
+ end
363
+ end
364
+
140
365
  describe LiteralMatcher do
141
366
  before :each do
142
367
  @matcher = LiteralMatcher.new(42)
@@ -183,4 +408,135 @@ module Machete::Matchers
183
408
  end
184
409
  end
185
410
  end
411
+
412
+ describe StartsWithMatcher do
413
+ before :each do
414
+ @matcher = StartsWithMatcher.new("abcd")
415
+ end
416
+
417
+ describe "initialize" do
418
+ it "sets attributes correctly" do
419
+ @matcher.prefix.should == "abcd"
420
+ end
421
+ end
422
+
423
+ describe "==" do
424
+ it "returns true when passed the same object" do
425
+ @matcher.should == @matcher
426
+ end
427
+
428
+ it "returns true when passed a StartsWithMatcher initialized with the same parameters" do
429
+ @matcher.should == StartsWithMatcher.new("abcd")
430
+ end
431
+
432
+ it "returns false when passed some random object" do
433
+ @matcher.should_not == Object.new
434
+ end
435
+
436
+ it "returns false when passed a subclass of StartsWithMatcher initialized with the same parameters" do
437
+ class SubclassedStartsWithMatcher < StartsWithMatcher
438
+ end
439
+
440
+ @matcher.should_not == SubclassedStartsWithMatcher.new("abcd")
441
+ end
442
+
443
+ it "returns false when passed a StartsWithMatcher initialized with different parameters" do
444
+ @matcher.should_not == StartsWithMatcher.new("efgh")
445
+ end
446
+ end
447
+
448
+ describe "matches?" do
449
+ it "matches a string starting with the prefix" do
450
+ @matcher.matches?("abcdefgh").should be_true
451
+ end
452
+
453
+ it "does not match a string not starting with the prefix" do
454
+ @matcher.matches?("efghijkl").should be_false
455
+ end
456
+
457
+ it "does not match some random object" do
458
+ @matcher.matches?(Object.new).should be_false
459
+ end
460
+ end
461
+ end
462
+
463
+ describe EndsWithMatcher do
464
+ before :each do
465
+ @matcher = EndsWithMatcher.new("abcd")
466
+ end
467
+
468
+ describe "initialize" do
469
+ it "sets attributes correctly" do
470
+ @matcher.suffix.should == "abcd"
471
+ end
472
+ end
473
+
474
+ describe "==" do
475
+ it "returns true when passed the same object" do
476
+ @matcher.should == @matcher
477
+ end
478
+
479
+ it "returns true when passed a EndsWithMatcher initialized with the same parameters" do
480
+ @matcher.should == EndsWithMatcher.new("abcd")
481
+ end
482
+
483
+ it "returns false when passed some random object" do
484
+ @matcher.should_not == Object.new
485
+ end
486
+
487
+ it "returns false when passed a subclass of EndsWithMatcher initialized with the same parameters" do
488
+ class SubclassedEndsWithMatcher < EndsWithMatcher
489
+ end
490
+
491
+ @matcher.should_not == SubclassedEndsWithMatcher.new("abcd")
492
+ end
493
+
494
+ it "returns false when passed a EndsWithMatcher initialized with different parameters" do
495
+ @matcher.should_not == EndsWithMatcher.new("efgh")
496
+ end
497
+ end
498
+
499
+ describe "matches?" do
500
+ it "matches a string ending with the suffix" do
501
+ @matcher.matches?("efghabcd").should be_true
502
+ end
503
+
504
+ it "does not match a string not ending with the suffix" do
505
+ @matcher.matches?("ijklefgh").should be_false
506
+ end
507
+
508
+ it "does not match some random object" do
509
+ @matcher.matches?(Object.new).should be_false
510
+ end
511
+ end
512
+ end
513
+
514
+ describe AnyMatcher do
515
+ before :each do
516
+ @matcher = AnyMatcher.new
517
+ end
518
+
519
+ describe "==" do
520
+ it "returns true when passed the same object" do
521
+ @matcher.should == @matcher
522
+ end
523
+
524
+ it "returns false when passed some random object" do
525
+ @matcher.should_not == Object.new
526
+ end
527
+
528
+ it "returns false when passed a subclass of AnyMatcher" do
529
+ class SubclassedAnyMatcher < AnyMatcher
530
+ end
531
+
532
+ @matcher.should_not == SubclassedAnyMatcher.new
533
+ end
534
+ end
535
+
536
+ describe "matches?" do
537
+ it "matches any object" do
538
+ @matcher.matches?(Object.new)
539
+ end
540
+ end
541
+ end
186
542
  end