citrus 1.6.0 → 1.7.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/README CHANGED
@@ -10,7 +10,7 @@ elegance and expressiveness of the language with the simplicity and power of
10
10
  parsing expressions.
11
11
 
12
12
 
13
- ** Installation **
13
+ = Installation
14
14
 
15
15
 
16
16
  Via RubyGems:
@@ -24,7 +24,7 @@ From a local copy:
24
24
  $ rake package && sudo rake install
25
25
 
26
26
 
27
- ** Background **
27
+ = Background
28
28
 
29
29
 
30
30
  In order to be able to use Citrus effectively, you must first understand the
@@ -99,7 +99,7 @@ available to them including the text of the match, its position in the input,
99
99
  and any submatches.
100
100
 
101
101
 
102
- ** Syntax **
102
+ = Syntax
103
103
 
104
104
 
105
105
  The most straightforward way to compose a Citrus grammar is to use Citrus' own
@@ -168,7 +168,7 @@ Note that any operator binds more tightly than the bar.
168
168
  == Super
169
169
 
170
170
  When including a grammar inside another, all rules in the child that have the
171
- same name as a rule in the parent also have access to the super keyword to
171
+ same name as a rule in the parent also have access to the "super" keyword to
172
172
  invoke the parent rule.
173
173
 
174
174
  == Labels
@@ -181,8 +181,31 @@ immediately preceding any expression.
181
181
  # expression may be referred to as "chars"
182
182
  # in a block method
183
183
 
184
+ == Precedence
184
185
 
185
- ** Example **
186
+ The following table contains a list of all operators and their precedence. A
187
+ higher level of precedence indicates tighter binding.
188
+
189
+ Operator | Level of Precedence | Name
190
+ ----------------------------------------------------------------
191
+ '' | 6 | Literal string
192
+ "" | 6 | Literal string
193
+ [] | 6 | Character class
194
+ . | 6 | Any character (dot)
195
+ // | 6 | Regular expression
196
+ () | 6 | Grouping
197
+ * | 5 | Repetition (arbitrary)
198
+ + | 5 | Repetition (one or more)
199
+ ? | 5 | Repetition (zero or one)
200
+ & | 4 | And predicate
201
+ ! | 4 | Not predicate
202
+ : | 4 | Label
203
+ <>, {} | 3 | Extension
204
+ e1 e2 | 2 | Sequence
205
+ e1 | e2 | 1 | Ordered choice
206
+
207
+
208
+ = Example
186
209
 
187
210
 
188
211
  Below is an example of a simple grammar that is able to parse strings of
@@ -300,7 +323,7 @@ Take a look at examples/calc.citrus for an example of a calculator that is able
300
323
  to parse and evaluate more complex mathematical expressions.
301
324
 
302
325
 
303
- ** Links **
326
+ = Links
304
327
 
305
328
 
306
329
  http://mjijackson.com/citrus
@@ -309,7 +332,7 @@ http://en.wikipedia.org/wiki/Parsing_expression_grammar
309
332
  http://treetop.rubyforge.org/index.html
310
333
 
311
334
 
312
- ** License **
335
+ = License
313
336
 
314
337
 
315
338
  Copyright 2010 Michael Jackson
@@ -1,6 +1,8 @@
1
1
  # A grammar for mathematical formulas that apply the basic four operations to
2
2
  # non-negative numbers (integers and floats), respecting operator precedence and
3
3
  # ignoring whitespace.
4
+ #
5
+ # An identical grammar that is written using pure Ruby can be found in calc.rb.
4
6
  grammar Calc
5
7
  rule term
6
8
  additive | factor
@@ -3,6 +3,9 @@ require 'citrus'
3
3
  # A grammar for mathematical formulas that apply the basic four operations to
4
4
  # non-negative numbers (integers and floats), respecting operator precedence and
5
5
  # ignoring whitespace.
6
+ #
7
+ # An identical grammar that is written using Citrus' own grammar syntax can be
8
+ # found in calc.citrus.
6
9
  grammar :Calc do
7
10
  rule :term do
8
11
  any(:additive, :factor)
@@ -0,0 +1,82 @@
1
+ # The grammars in this file conform to the ABNF given in Appendix A of RFC 3986
2
+ # Uniform Resource Identifier (URI): Generic Syntax.
3
+ #
4
+ # See http://tools.ietf.org/html/rfc3986#appendix-A for more information.
5
+
6
+ grammar IPv4Address
7
+ # A host identified by an IPv4 literal address is represented in
8
+ # dotted-decimal notation (a sequence of four decimal numbers in the
9
+ # range 0 to 255, separated by "."), as described in [RFC1123] by
10
+ # reference to [RFC0952]. Note that other forms of dotted notation may
11
+ # be interpreted on some platforms, as described in Section 7.4, but
12
+ # only the dotted-decimal form of four octets is allowed by this
13
+ # grammar.
14
+ rule IPv4address
15
+ (dec-octet '.' dec-octet '.' dec-octet '.' dec-octet) {
16
+ def version; 4 end
17
+ }
18
+ end
19
+
20
+ rule dec-octet
21
+ '25' [0-5] # 250-255
22
+ | '2' [0-4] DIGIT # 200-249
23
+ | '1' DIGIT DIGIT # 100-199
24
+ | [1-9] DIGIT # 10-99
25
+ | DIGIT # 0-9
26
+ end
27
+
28
+ rule DIGIT
29
+ [0-9]
30
+ end
31
+ end
32
+
33
+ grammar IPv6Address
34
+ include IPv4Address
35
+
36
+ # A 128-bit IPv6 address is divided into eight 16-bit pieces. Each
37
+ # piece is represented numerically in case-insensitive hexadecimal,
38
+ # using one to four hexadecimal digits (leading zeroes are permitted).
39
+ # The eight encoded pieces are given most-significant first, separated
40
+ # by colon characters. Optionally, the least-significant two pieces
41
+ # may instead be represented in IPv4 address textual format. A
42
+ # sequence of one or more consecutive zero-valued 16-bit pieces within
43
+ # the address may be elided, omitting all their digits and leaving
44
+ # exactly two consecutive colons in their place to mark the elision.
45
+ rule IPv6address
46
+ ( (h16 ":")6*6 ls32
47
+ | "::" (h16 ":")5*5 ls32
48
+ | h16 ? "::" (h16 ":")4*4 ls32
49
+ | (h16 (":" h16)*1)? "::" (h16 ":")3*3 ls32
50
+ | (h16 (":" h16)*2)? "::" (h16 ":")2*2 ls32
51
+ | (h16 (":" h16)*3)? "::" h16 ":" ls32
52
+ | (h16 (":" h16)*4)? "::" ls32
53
+ | (h16 (":" h16)*5)? "::" h16
54
+ | (h16 (":" h16)*6)? "::"
55
+ ) {
56
+ def version; 6 end
57
+ }
58
+ end
59
+
60
+ # 16-bit address represented in hexadecimal.
61
+ rule h16
62
+ HEXDIG 1*4
63
+ end
64
+
65
+ # Least-significant 32 bits of address.
66
+ rule ls32
67
+ (h16 ":" h16) | IPv4address
68
+ end
69
+
70
+ rule HEXDIG
71
+ DIGIT | [a-fA-F] # Hexadecimal should be case-insensitive.
72
+ end
73
+ end
74
+
75
+ grammar IPAddress
76
+ include IPv4Address
77
+ include IPv6Address
78
+
79
+ rule IPaddress
80
+ IPv4address | IPv6address
81
+ end
82
+ end
@@ -0,0 +1,75 @@
1
+ require 'citrus'
2
+
3
+ # This file contains a small suite of tests for the grammars found in ip.citrus.
4
+ # If this file is run directly (i.e. using `ruby ip.rb') the tests will run.
5
+ # Otherwise, this file may be required by another that needs access to the IP
6
+ # address grammars just as any other file would be.
7
+
8
+ # Load and evaluate the grammars contained in ip.citrus into the global
9
+ # namespace.
10
+ Citrus.load(File.expand_path('../ip.citrus', __FILE__))
11
+
12
+ if $0 == __FILE__
13
+ require 'test/unit'
14
+
15
+ class IPAddressTest < Test::Unit::TestCase
16
+ def test_dec_octet
17
+ match = IPv4Address.parse('0', :root => :'dec-octet')
18
+ assert(match)
19
+
20
+ match = IPv4Address.parse('255', :root => :'dec-octet')
21
+ assert(match)
22
+ end
23
+
24
+ def test_hexdig
25
+ match = IPv6Address.parse('0', :root => :HEXDIG)
26
+ assert(match)
27
+
28
+ match = IPv6Address.parse('A', :root => :HEXDIG)
29
+ assert(match)
30
+ end
31
+
32
+ def test_v4
33
+ match = IPv4Address.parse('0.0.0.0')
34
+ assert(match)
35
+
36
+ match = IPv4Address.parse('255.255.255.255')
37
+ assert(match)
38
+
39
+ assert_raise Citrus::ParseError do
40
+ IPv4Address.parse('255.255.255')
41
+ end
42
+ end
43
+
44
+ def test_v6
45
+ match = IPv6Address.parse('1:2:3:4:5:6:7:8')
46
+ assert(match)
47
+
48
+ match = IPv6Address.parse('12AD:34FC:A453:1922::')
49
+ assert(match)
50
+
51
+ match = IPv6Address.parse('12AD::34FC')
52
+ assert(match)
53
+
54
+ match = IPv6Address.parse('12AD::')
55
+ assert(match)
56
+
57
+ match = IPv6Address.parse('::')
58
+ assert(match)
59
+
60
+ assert_raise Citrus::ParseError do
61
+ IPv6Address.parse('1:2')
62
+ end
63
+ end
64
+
65
+ def test_all
66
+ match = IPAddress.parse('1.2.3.4')
67
+ assert(match)
68
+ assert_equal(4, match.version)
69
+
70
+ match = IPAddress.parse('1:2:3:4::')
71
+ assert(match)
72
+ assert_equal(6, match.version)
73
+ end
74
+ end
75
+ end
@@ -16,7 +16,7 @@ syn case match
16
16
  syn match ctDoubleColon "::" contained
17
17
  syn match ctConstant "\u\w*" contained
18
18
  syn match ctModule "\(\(::\)\?\u\w*\)\+" contains=ctDoubleColon,ctConstant contained
19
- syn match ctVariable "[a-zA-Z_-]\+" contained
19
+ syn match ctVariable "\a[a-zA-Z0-9_-]*" contained
20
20
 
21
21
  " Comments
22
22
  syn match ctComment "#.*" contains=@Spell
@@ -4,7 +4,7 @@
4
4
  #
5
5
  # http://mjijackson.com/citrus
6
6
  module Citrus
7
- VERSION = [1, 6, 0]
7
+ VERSION = [1, 7, 0]
8
8
 
9
9
  Infinity = 1.0 / 0
10
10
 
@@ -68,7 +68,7 @@ module Citrus
68
68
  # created with this method may be assigned a name by being assigned to some
69
69
  # constant, e.g.:
70
70
  #
71
- # Calc = Grammar.new {}
71
+ # Calc = Citrus::Grammar.new {}
72
72
  #
73
73
  def self.new(&block)
74
74
  mod = Module.new { include Grammar }
@@ -143,8 +143,8 @@ module Citrus
143
143
  end
144
144
 
145
145
  # Gets/sets the rule with the given +name+. If +obj+ is given the rule
146
- # will be set to the value of +obj+ passed through Rule#create. If a block
147
- # is given, its return value will be used for the value of +obj+.
146
+ # will be set to the value of +obj+ passed through Rule#new. If a block is
147
+ # given, its return value will be used for the value of +obj+.
148
148
  #
149
149
  # It is important to note that this method will also check any included
150
150
  # grammars for a rule with the given +name+ if one cannot be found in this
@@ -156,7 +156,7 @@ module Citrus
156
156
  if obj
157
157
  rule_names << sym unless has_rule?(sym)
158
158
 
159
- rule = Rule.create(obj)
159
+ rule = Rule.new(obj)
160
160
  rule.name = name
161
161
  setup_super(rule, name)
162
162
  rule.grammar = self
@@ -239,7 +239,7 @@ module Citrus
239
239
  # the given +rule+. A block may also be given that will be used to create
240
240
  # an anonymous module. See Rule#ext=.
241
241
  def ext(rule, mod=nil)
242
- rule = Rule.create(rule)
242
+ rule = Rule.new(rule)
243
243
  mod = Proc.new if block_given?
244
244
  rule.extension = mod if mod
245
245
  rule
@@ -342,7 +342,7 @@ module Citrus
342
342
  # Input during parsing.
343
343
  module Rule
344
344
  # Returns a new Rule object depending on the type of object given.
345
- def self.create(obj)
345
+ def self.new(obj)
346
346
  case obj
347
347
  when Rule then obj
348
348
  when Symbol then Alias.new(obj)
@@ -356,6 +356,14 @@ module Citrus
356
356
  end
357
357
  end
358
358
 
359
+ # Creates a new rule object from the given expression.
360
+ #
361
+ # Citrus::Rule.create('"a" | "b"')
362
+ #
363
+ def self.create(expr)
364
+ File.parse(expr, :root => :rule_body).value
365
+ end
366
+
359
367
  @unique_id = 0
360
368
 
361
369
  # Generates a new rule id.
@@ -587,7 +595,7 @@ module Citrus
587
595
  include Rule
588
596
 
589
597
  def initialize(rules=[])
590
- @rules = rules.map {|r| Rule.create(r) }
598
+ @rules = rules.map {|r| Rule.new(r) }
591
599
  end
592
600
 
593
601
  # An array of the actual Rule objects this rule uses to match.
@@ -75,7 +75,7 @@ module Citrus
75
75
  end
76
76
 
77
77
  rule :sequence do
78
- zero_or_more(:prefix) {
78
+ zero_or_more(:appendix) {
79
79
  def values
80
80
  matches.map {|m| m.value }
81
81
  end
@@ -86,44 +86,44 @@ module Citrus
86
86
  }
87
87
  end
88
88
 
89
- rule :prefix do
90
- all(zero_or_one(:qualifier), :appendix) {
89
+ rule :appendix do
90
+ all(:prefix, zero_or_one(:extension)) {
91
91
  def value
92
- rule = appendix.value
93
- qualifier = matches[0].first
94
- rule = qualifier.wrap(rule) if qualifier
92
+ rule = prefix.value
93
+ extension = matches[1].first
94
+ rule.extension = extension.value if extension
95
95
  rule
96
96
  end
97
97
  }
98
98
  end
99
99
 
100
- rule :appendix do
101
- all(:suffix, zero_or_one(:extension)) {
100
+ rule :prefix do
101
+ all(zero_or_one(:predicate), :suffix) {
102
102
  def value
103
103
  rule = suffix.value
104
- extension = matches[1].first
105
- extension.apply(rule) if extension
104
+ predicate = matches[0].first
105
+ rule = predicate.wrap(rule) if predicate
106
106
  rule
107
107
  end
108
108
  }
109
109
  end
110
110
 
111
111
  rule :suffix do
112
- all(:primary, zero_or_one(:quantifier)) {
112
+ all(:primary, zero_or_one(:repeat)) {
113
113
  def value
114
114
  rule = primary.value
115
- quantifier = matches[1].first
116
- rule = quantifier.wrap(rule) if quantifier
115
+ repeat = matches[1].first
116
+ rule = repeat.wrap(rule) if repeat
117
117
  rule
118
118
  end
119
119
  }
120
120
  end
121
121
 
122
122
  rule :primary do
123
- any(:super, :alias, :rule_body_paren, :terminal)
123
+ any(:super, :alias, :grouping, :terminal)
124
124
  end
125
125
 
126
- rule :rule_body_paren do
126
+ rule :grouping do
127
127
  all(:lparen, :rule_body, :rparen) {
128
128
  def value
129
129
  rule_body.value
@@ -157,8 +157,10 @@ module Citrus
157
157
  }
158
158
  end
159
159
 
160
+ # Rule names may contain letters, numbers, underscores, and dashes. They
161
+ # MUST start with a letter.
160
162
  rule :rule_name do
161
- all(/[a-zA-Z_-]+/, :space) {
163
+ all(/[a-zA-Z][a-zA-Z0-9_-]*/, :space) {
162
164
  def value
163
165
  first.text
164
166
  end
@@ -184,7 +186,7 @@ module Citrus
184
186
  rule :terminal do
185
187
  any(:quoted_string, :character_class, :anything_symbol, :regular_expression) {
186
188
  def value
187
- Rule.create(super)
189
+ Rule.new(super)
188
190
  end
189
191
  }
190
192
  end
@@ -221,7 +223,7 @@ module Citrus
221
223
  }
222
224
  end
223
225
 
224
- rule :qualifier do
226
+ rule :predicate do
225
227
  any(:and, :not, :label)
226
228
  end
227
229
 
@@ -254,11 +256,7 @@ module Citrus
254
256
  end
255
257
 
256
258
  rule :extension do
257
- any(:tag, :block) {
258
- def apply(rule)
259
- rule.extension = value
260
- end
261
- }
259
+ any(:tag, :block)
262
260
  end
263
261
 
264
262
  rule :tag do
@@ -277,8 +275,8 @@ module Citrus
277
275
  }
278
276
  end
279
277
 
280
- rule :quantifier do
281
- any(:question, :plus, :repeat) {
278
+ rule :repeat do
279
+ any(:question, :plus, :star_quantity) {
282
280
  def wrap(rule)
283
281
  Repeat.new(min, max, rule)
284
282
  end
@@ -299,7 +297,7 @@ module Citrus
299
297
  }
300
298
  end
301
299
 
302
- rule :repeat do
300
+ rule :star_quantity do
303
301
  all(/[0-9]*/, '*', /[0-9]*/, :space) {
304
302
  def min
305
303
  matches[0] == '' ? 0 : matches[0].text.to_i
@@ -105,6 +105,13 @@ class CitrusFileTest < Test::Unit::TestCase
105
105
  assert_kind_of(Rule, match.value)
106
106
  assert_instance_of(Repeat, match.value)
107
107
 
108
+ match = grammar.parse('"a"*')
109
+ assert(match)
110
+ assert_kind_of(Rule, match.value)
111
+ assert_instance_of(Repeat, match.value)
112
+ assert_equal(0, match.value.min)
113
+ assert_equal(Infinity, match.value.max)
114
+
108
115
  match = grammar.parse('("a" "b")*')
109
116
  assert(match)
110
117
  assert_kind_of(Rule, match.value)
@@ -469,8 +476,8 @@ class CitrusFileTest < Test::Unit::TestCase
469
476
  assert_equal(/a/i, match.value)
470
477
  end
471
478
 
472
- def test_qualifier
473
- grammar = file(:qualifier)
479
+ def test_predicate
480
+ grammar = file(:predicate)
474
481
 
475
482
  match = grammar.parse('&')
476
483
  assert(match)
@@ -570,8 +577,8 @@ class CitrusFileTest < Test::Unit::TestCase
570
577
  assert(match.value)
571
578
  end
572
579
 
573
- def test_quantifier
574
- grammar = file(:quantifier)
580
+ def test_repeat
581
+ grammar = file(:repeat)
575
582
 
576
583
  match = grammar.parse('?')
577
584
  assert(match)
@@ -20,6 +20,13 @@ class RuleTest < Test::Unit::TestCase
20
20
 
21
21
  NumericModule = Module.new(&NumericProc)
22
22
 
23
+ def test_create
24
+ rule = Rule.create('"a"')
25
+ assert(rule)
26
+ match = rule.match(input('a'))
27
+ assert(match)
28
+ end
29
+
23
30
  def test_match_module
24
31
  rule = EqualRule.new('a')
25
32
  rule.extension = MatchModule
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 1
7
- - 6
7
+ - 7
8
8
  - 0
9
- version: 1.6.0
9
+ version: 1.7.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Michael Jackson
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-08-17 00:00:00 -06:00
17
+ date: 2010-08-17 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -63,6 +63,8 @@ files:
63
63
  - doc/syntax.rdoc
64
64
  - examples/calc.citrus
65
65
  - examples/calc.rb
66
+ - examples/ip.citrus
67
+ - examples/ip.rb
66
68
  - extras/citrus.vim
67
69
  - lib/citrus/debug.rb
68
70
  - lib/citrus/file.rb