citrus 1.6.0 → 1.7.0

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