citrus 3.0.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2caf741a96f7499506f5bf450998aad817dbe9d4
4
+ data.tar.gz: 955838c4b13b2ae7c25be7b7d8e440a594105be8
5
+ SHA512:
6
+ metadata.gz: 3b02bb8198f6190575e3e4960be9d81adcca08d638919e32a6b1b1dc164e953007ce60b6ed86e0988a51a3c81b2ad490f40863e9ed61f06a58e50193a055e03f
7
+ data.tar.gz: 3a5b292f74e1849e01ebe3349c402a4ad3d7f9c1f0a024b21f830c3f10700063f9551ebd601155070992f9d636cca4e6609dd00e4e72faa8400c098d622510ad
data/CHANGES CHANGED
@@ -1,9 +1,54 @@
1
+ = 3.0.1 / 2014-03-14
2
+
3
+ * Fixed bad 3.0.0 release.
4
+
1
5
  = 3.0.0 / 2014-03-14
2
6
 
3
7
  * Moved Object#grammar to citrus/core_ext.rb. Citrus no longer installs core
4
8
  extensions by default. Use "require 'citrus/core_ext.rb'" instead of
5
9
  "require 'citrus'" to keep the previous behavior.
6
10
 
11
+ * Removed Match#method_missing, added #capture(name) and #captures(name)
12
+
13
+ Match#method_missing is unsafe as illustrated in Github issue #41. In
14
+ particular, it makes composing a grammar with aribitrary gems unsafe (e.g.
15
+ when the latter make core extensions), leads to unexpected results with
16
+ labels match existing Kernel methods (e.g. `p`), and prevents Match from
17
+ getting new methods in a backward compatible way. This commit therefore
18
+ removes it.
19
+
20
+ In Citrus 2.x, method_missing allowed rule productions to denote captured
21
+ matches by label name:
22
+
23
+ rule pair
24
+ (foo ':' bar) {
25
+ [foo.value, bar.value]
26
+ }
27
+ end
28
+
29
+ Also, it allowed invoking String operators on the Match's text:
30
+
31
+ rule int
32
+ [0-9]+ { to_i }
33
+ end
34
+
35
+ Those two scenarios no longer work out of the box in Citrus 3.0. You must
36
+ use capture(label) for the former, and to_str for the latter:
37
+
38
+ rule pair
39
+ (foo ':' bar) {
40
+ [capture(:foo).value, capture(:bar).value]
41
+ }
42
+ end
43
+
44
+ rule int
45
+ [0-9]+ { to_str.to_i }
46
+ end
47
+
48
+ Match#captures now accepts an optional label name as first argument and
49
+ returns the corresponding array of matches for that label (useful in case
50
+ the label belongs to a repetition).
51
+
7
52
  = 2.5.0 / 2014-03-13
8
53
 
9
54
  * Inputs may be generated from many different sources, including Pathname and
data/README.md CHANGED
@@ -232,8 +232,8 @@ Additionally, extensions may be specified inline using curly braces. When using
232
232
  this method, the code inside the curly braces may be invoked by calling the
233
233
  `value` method on the match object.
234
234
 
235
- [0-9] { to_i } # match any digit and return its integer value when
236
- # calling the #value method on the match object
235
+ [0-9] { to_str.to_i } # match any digit and return its integer value when
236
+ # calling the #value method on the match object
237
237
 
238
238
  Note that when using the inline block method you may also specify arguments in
239
239
  between vertical bars immediately following the opening curly brace, just like
@@ -358,13 +358,13 @@ blocks. Let's extend the `Addition` grammar using this technique.
358
358
  grammar Addition
359
359
  rule additive
360
360
  (number plus term:(additive | number)) {
361
- number.value + term.value
361
+ capture(:number).value + capture(:term).value
362
362
  }
363
363
  end
364
364
 
365
365
  rule number
366
366
  ([0-9]+ space) {
367
- to_i
367
+ to_str.to_i
368
368
  }
369
369
  end
370
370
 
@@ -383,17 +383,19 @@ execute by calling `value` on match objects that result from those rules. It's
383
383
  easiest to explain what is going on here by starting with the lowest level
384
384
  block, which is defined within `number`.
385
385
 
386
- Inside this block we see a call to another method, namely `to_i`. When called in
387
- the context of a match object, methods that are not defined may be called on a
388
- match's internal string object via `method_missing`. Thus, the call to `to_i`
389
- should return the integer value of the match.
386
+ Inside this block we see a call to another method, namely `to_str`. When called
387
+ in the context of a match object, this method returns the match's internal
388
+ string object. Thus, the call to `to_str.to_i` should return the integer value
389
+ of the match.
390
390
 
391
391
  Similarly, matches created by `additive` will also have a `value` method. Notice
392
392
  the use of the `term` label within the rule definition. This label allows the
393
393
  match that is created by the choice between `additive` and `number` to be
394
- retrieved using the `term` method. The value of an additive match is determined
394
+ retrieved using `capture(:term)`. The value of an additive match is determined
395
395
  to be the values of its `number` and `term` matches added together using Ruby's
396
- addition operator.
396
+ addition operator. Note that the plural form `captures(:term)` can be used to
397
+ get an array of matches for a given label (e.g. when the label belongs to a
398
+ repetition).
397
399
 
398
400
  Since `additive` is the first rule defined in the grammar, any match that
399
401
  results from parsing a string with this grammar will have a `value` method that
@@ -441,11 +443,11 @@ look like this:
441
443
  rule additive
442
444
  (number plus term:(additive | number)) {
443
445
  def lhs
444
- number.value
446
+ capture(:number).value
445
447
  end
446
448
 
447
449
  def rhs
448
- term.value
450
+ capture(:term).value
449
451
  end
450
452
 
451
453
  def value
@@ -475,11 +477,11 @@ define the following module.
475
477
 
476
478
  module Additive
477
479
  def lhs
478
- number.value
480
+ capture(:number).value
479
481
  end
480
482
 
481
483
  def rhs
482
- term.value
484
+ capture(:term).value
483
485
  end
484
486
 
485
487
  def value
@@ -1326,9 +1326,14 @@ module Citrus
1326
1326
 
1327
1327
  # Returns a hash of capture names to arrays of matches with that name,
1328
1328
  # in the order they appeared in the input.
1329
- def captures
1329
+ def captures(name = nil)
1330
1330
  process_events! unless @captures
1331
- @captures
1331
+ name ? @captures[name] : @captures
1332
+ end
1333
+
1334
+ # Convenient method for captures[name].first.
1335
+ def capture(name)
1336
+ captures[name].first
1332
1337
  end
1333
1338
 
1334
1339
  # Returns an array of all immediate submatches of this match.
@@ -1342,20 +1347,6 @@ module Citrus
1342
1347
  matches.first
1343
1348
  end
1344
1349
 
1345
- # Allows methods of this match's string to be called directly and provides
1346
- # a convenient interface for retrieving the first match with a given name.
1347
- def method_missing(sym, *args, &block)
1348
- unless defined?(Citrus::METHOD_MISSING_WARNED)
1349
- warn("[`#{sym}`] Citrus::Match#method_missing is unsafe and will be removed in 3.0. Use captures.")
1350
- Citrus.send(:const_set, :METHOD_MISSING_WARNED, true)
1351
- end
1352
- if string.respond_to?(sym)
1353
- string.__send__(sym, *args, &block)
1354
- else
1355
- captures[sym].first
1356
- end
1357
- end
1358
-
1359
1350
  alias_method :to_s, :string
1360
1351
 
1361
1352
  # This alias allows strings to be compared to the string value of Match
@@ -6,6 +6,10 @@ module Citrus
6
6
  # Some helper methods for rules that alias +module_name+ and don't want to
7
7
  # use +Kernel#eval+ to retrieve Module objects.
8
8
  module ModuleNameHelpers #:nodoc:
9
+ def module_name
10
+ capture(:module_name)
11
+ end
12
+
9
13
  def module_segments
10
14
  @module_segments ||= module_name.value.split('::')
11
15
  end
@@ -58,6 +62,7 @@ module Citrus
58
62
  captures[:include].each {|inc| grammar.include(inc.value) }
59
63
  captures[:rule].each {|r| grammar.rule(r.rule_name.value, r.value) }
60
64
 
65
+ root = capture(:root)
61
66
  grammar.root(root.value) if root
62
67
 
63
68
  grammar
@@ -66,10 +71,17 @@ module Citrus
66
71
  end
67
72
 
68
73
  rule :rule do
69
- all(:rule_keyword, :rule_name, zero_or_one(:expression), :end_keyword) {
70
- # An empty rule definition matches the empty string.
71
- expression ? expression.value : Rule.for('')
72
- }
74
+ mod all(:rule_keyword, :rule_name, zero_or_one(:expression), :end_keyword) do
75
+ def rule_name
76
+ capture(:rule_name)
77
+ end
78
+
79
+ def value
80
+ # An empty rule definition matches the empty string.
81
+ expr = capture(:expression)
82
+ expr ? expr.value : Rule.for('')
83
+ end
84
+ end
73
85
  end
74
86
 
75
87
  rule :expression do
@@ -88,7 +100,8 @@ module Citrus
88
100
 
89
101
  rule :labelled do
90
102
  all(zero_or_one(:label), :extended) {
91
- rule = extended.value
103
+ label = capture(:label)
104
+ rule = capture(:extended).value
92
105
  rule.label = label.value if label
93
106
  rule
94
107
  }
@@ -96,7 +109,8 @@ module Citrus
96
109
 
97
110
  rule :extended do
98
111
  all(:prefix, zero_or_one(:extension)) {
99
- rule = prefix.value
112
+ extension = capture(:extension)
113
+ rule = capture(:prefix).value
100
114
  rule.extension = extension.value if extension
101
115
  rule
102
116
  }
@@ -104,7 +118,8 @@ module Citrus
104
118
 
105
119
  rule :prefix do
106
120
  all(zero_or_one(:predicate), :suffix) {
107
- rule = suffix.value
121
+ predicate = capture(:predicate)
122
+ rule = capture(:suffix).value
108
123
  rule = predicate.value(rule) if predicate
109
124
  rule
110
125
  }
@@ -112,7 +127,8 @@ module Citrus
112
127
 
113
128
  rule :suffix do
114
129
  all(:primary, zero_or_one(:repeat)) {
115
- rule = primary.value
130
+ repeat = capture(:repeat)
131
+ rule = capture(:primary).value
116
132
  rule = repeat.value(rule) if repeat
117
133
  rule
118
134
  }
@@ -124,7 +140,7 @@ module Citrus
124
140
 
125
141
  rule :grouping do
126
142
  all(['(', zero_or_one(:space)], :expression, [')', zero_or_one(:space)]) {
127
- expression.value
143
+ capture(:expression).value
128
144
  }
129
145
  end
130
146
 
@@ -132,7 +148,7 @@ module Citrus
132
148
 
133
149
  rule :require do
134
150
  all(:require_keyword, :quoted_string) {
135
- quoted_string.value
151
+ capture(:quoted_string).value
136
152
  }
137
153
  end
138
154
 
@@ -148,7 +164,7 @@ module Citrus
148
164
 
149
165
  rule :root do
150
166
  all(:root_keyword, :rule_name) {
151
- rule_name.value
167
+ capture(:rule_name).value
152
168
  }
153
169
  end
154
170
 
@@ -172,7 +188,7 @@ module Citrus
172
188
 
173
189
  rule :alias do
174
190
  all(notp(:end_keyword), :rule_name) {
175
- Alias.new(rule_name.value)
191
+ Alias.new(capture(:rule_name).value)
176
192
  }
177
193
  end
178
194
 
@@ -232,7 +248,7 @@ module Citrus
232
248
 
233
249
  rule :label do
234
250
  all(/[a-zA-Z0-9_]+/, :space, ':', :space) {
235
- first.to_sym
251
+ first.to_str.to_sym
236
252
  }
237
253
  end
238
254
 
@@ -312,8 +328,8 @@ module Citrus
312
328
 
313
329
  rule :star do
314
330
  all(/[0-9]*/, '*', /[0-9]*/, :space) { |rule|
315
- min = captures[1] == '' ? 0 : captures[1].to_i
316
- max = captures[3] == '' ? Infinity : captures[3].to_i
331
+ min = captures[1] == '' ? 0 : captures[1].to_str.to_i
332
+ max = captures[3] == '' ? Infinity : captures[3].to_str.to_i
317
333
  Repeat.new(rule, min, max)
318
334
  }
319
335
  end
@@ -1,6 +1,6 @@
1
1
  module Citrus
2
2
  # The current version of Citrus as [major, minor, patch].
3
- VERSION = [3, 0, 0]
3
+ VERSION = [3, 0, 1]
4
4
 
5
5
  # Returns the current version of Citrus as a string.
6
6
  def self.version
@@ -9,12 +9,12 @@ class ExtensionTest < Test::Unit::TestCase
9
9
 
10
10
  module NumericModule
11
11
  def add_one
12
- to_i + 1
12
+ to_str.to_i + 1
13
13
  end
14
14
  end
15
15
 
16
16
  NumericProcBare = Proc.new {
17
- to_i + 1
17
+ to_str.to_i + 1
18
18
  }
19
19
 
20
20
  def test_match_module
@@ -149,6 +149,13 @@ class GrammarTest < Test::Unit::TestCase
149
149
  end
150
150
  end
151
151
 
152
+ def test_labeled_production
153
+ grammar = Grammar.new {
154
+ rule(:abc) { label('abc', :p){ capture(:p) } }
155
+ }
156
+ assert_equal('abc', grammar.parse('abc').value)
157
+ end
158
+
152
159
  def test_global_grammar
153
160
  assert_raise ArgumentError do
154
161
  grammar(:abc)
@@ -94,13 +94,13 @@ class MatchTest < Test::Unit::TestCase
94
94
  grammar :Addition do
95
95
  rule :additive do
96
96
  all(:number, :plus, label(any(:additive, :number), 'term')) {
97
- number.value + term.value
97
+ capture(:number).value + capture(:term).value
98
98
  }
99
99
  end
100
100
 
101
101
  rule :number do
102
102
  all(/[0-9]+/, :space) {
103
- strip.to_i
103
+ to_str.strip.to_i
104
104
  }
105
105
  end
106
106
 
@@ -139,4 +139,23 @@ class MatchTest < Test::Unit::TestCase
139
139
  assert(match.matches)
140
140
  assert_equal(3, match.matches.length)
141
141
  end
142
+
143
+ def test_capture
144
+ match = Addition.parse('1+2')
145
+ assert(match.capture(:number))
146
+ assert_equal(1, match.capture(:number).value)
147
+ end
148
+
149
+ def test_captures
150
+ match = Addition.parse('1+2')
151
+ assert(match.captures)
152
+ assert_equal(7, match.captures.size)
153
+ end
154
+
155
+ def test_captures_with_a_name
156
+ match = Addition.parse('1+2')
157
+ assert(match.captures(:number))
158
+ assert_equal(2, match.captures(:number).size)
159
+ assert_equal([1, 2], match.captures(:number).map(&:value))
160
+ end
142
161
  end
metadata CHANGED
@@ -1,30 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: citrus
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
5
- prerelease:
4
+ version: 3.0.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Michael Jackson
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2014-03-14 00:00:00.000000000 Z
11
+ date: 2014-03-15 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rake
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - '>='
20
18
  - !ruby/object:Gem::Version
21
19
  version: '0'
22
20
  type: :development
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - '>='
28
25
  - !ruby/object:Gem::Version
29
26
  version: '0'
30
27
  description: Parsing Expressions for Ruby
@@ -97,6 +94,7 @@ files:
97
94
  - CHANGES
98
95
  homepage: http://mjijackson.com/citrus
99
96
  licenses: []
97
+ metadata: {}
100
98
  post_install_message:
101
99
  rdoc_options:
102
100
  - --line-numbers
@@ -108,22 +106,20 @@ rdoc_options:
108
106
  require_paths:
109
107
  - lib
110
108
  required_ruby_version: !ruby/object:Gem::Requirement
111
- none: false
112
109
  requirements:
113
- - - ! '>='
110
+ - - '>='
114
111
  - !ruby/object:Gem::Version
115
112
  version: '0'
116
113
  required_rubygems_version: !ruby/object:Gem::Requirement
117
- none: false
118
114
  requirements:
119
- - - ! '>='
115
+ - - '>='
120
116
  - !ruby/object:Gem::Version
121
117
  version: '0'
122
118
  requirements: []
123
119
  rubyforge_project:
124
- rubygems_version: 1.8.23
120
+ rubygems_version: 2.0.3
125
121
  signing_key:
126
- specification_version: 3
122
+ specification_version: 4
127
123
  summary: Parsing Expressions for Ruby
128
124
  test_files:
129
125
  - test/alias_test.rb