machete 0.2.1 → 0.3.0

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