citrus 2.3.2 → 2.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README +30 -10
- data/Rakefile +1 -1
- data/citrus.gemspec +1 -1
- data/doc/syntax.markdown +9 -9
- data/examples/calc.citrus +2 -3
- data/examples/calc.rb +79 -72
- data/examples/ipaddress.citrus +16 -0
- data/examples/ipaddress.rb +23 -0
- data/examples/ipv4address.citrus +26 -0
- data/examples/ipv4address.rb +49 -0
- data/examples/{ip.citrus → ipv6address.citrus} +1 -40
- data/examples/ipv6address.rb +55 -0
- data/lib/citrus.rb +267 -157
- data/lib/citrus/file.rb +58 -64
- data/lib/citrus/version.rb +9 -0
- data/test/alias_test.rb +1 -1
- data/test/file_test.rb +101 -139
- data/test/helper.rb +0 -116
- data/test/match_test.rb +0 -1
- data/test/memoized_input_test.rb +1 -1
- data/test/multibyte_test.rb +57 -6
- data/test/parse_error_test.rb +4 -2
- metadata +112 -108
- data/examples/ip.rb +0 -77
- data/test/calc_file_test.rb +0 -16
- data/test/calc_test.rb +0 -11
data/README
CHANGED
@@ -206,15 +206,6 @@ levels of precedence is below.
|
|
206
206
|
|
207
207
|
See [Choice](api/classes/Citrus/Choice.html) for more information.
|
208
208
|
|
209
|
-
## Grouping
|
210
|
-
|
211
|
-
As is common in many programming languages, parentheses may be used to override
|
212
|
-
the normal binding order of operators. In the following example parentheses are
|
213
|
-
used to make the vertical bar between `'b'` and `'c'` bind tighter than the
|
214
|
-
space between `'a'` and `'b'`.
|
215
|
-
|
216
|
-
'a' ('b' | 'c') # match "a", then "b" or "c"
|
217
|
-
|
218
209
|
## Labels
|
219
210
|
|
220
211
|
Match objects may be referred to by a different name than the rule that
|
@@ -299,6 +290,15 @@ Operator | Name | Precedence
|
|
299
290
|
e1 e2 | Sequence | 2
|
300
291
|
e1 | e2 | Ordered choice | 1
|
301
292
|
|
293
|
+
## Grouping
|
294
|
+
|
295
|
+
As is common in many programming languages, parentheses may be used to override
|
296
|
+
the normal binding order of operators. In the following example parentheses are
|
297
|
+
used to make the vertical bar between `'b'` and `'c'` bind tighter than the
|
298
|
+
space between `'a'` and `'b'`.
|
299
|
+
|
300
|
+
'a' ('b' | 'c') # match "a", then "b" or "c"
|
301
|
+
|
302
302
|
|
303
303
|
# Example
|
304
304
|
|
@@ -510,7 +510,7 @@ case that could be used to test that our grammar works properly.
|
|
510
510
|
assert_equal('23 + 12', match)
|
511
511
|
assert_equal(35, match.value)
|
512
512
|
end
|
513
|
-
|
513
|
+
|
514
514
|
def test_number
|
515
515
|
match = Addition.parse('23', :root => :number)
|
516
516
|
assert(match)
|
@@ -567,9 +567,29 @@ To install the [Vim](http://www.vim.org/) scripts, copy the files in
|
|
567
567
|
[runtimepath](http://vimdoc.sourceforge.net/htmldoc/options.html#\'runtimepath\').
|
568
568
|
|
569
569
|
|
570
|
+
# Examples
|
571
|
+
|
572
|
+
|
573
|
+
The project source directory contains several example scripts that demonstrate
|
574
|
+
how grammars are to be constructed and used. Each Citrus file in the examples
|
575
|
+
directory has an accompanying Ruby file with the same name that contains a suite
|
576
|
+
of tests for that particular file.
|
577
|
+
|
578
|
+
The best way to run any of these examples is to pass the name of the Ruby file
|
579
|
+
directly to the Ruby interpreter on the command line, e.g.:
|
580
|
+
|
581
|
+
$ ruby -Ilib examples/calc.rb
|
582
|
+
|
583
|
+
This particular invocation uses the `-I` flag to ensure that you are using the
|
584
|
+
version of Citrus that was bundled with that particular example file (i.e. the
|
585
|
+
version that is contained in the `lib` directory).
|
586
|
+
|
587
|
+
|
570
588
|
# Links
|
571
589
|
|
572
590
|
|
591
|
+
Discussion around Citrus happens on the [citrus-users Google group](http://groups.google.com/group/citrus-users).
|
592
|
+
|
573
593
|
The primary resource for all things to do with parsing expressions can be found
|
574
594
|
on the original [Packrat and Parsing Expression Grammars page](http://pdos.csail.mit.edu/~baford/packrat)
|
575
595
|
at MIT.
|
data/Rakefile
CHANGED
@@ -6,7 +6,7 @@ task :default => :test
|
|
6
6
|
# TESTS #######################################################################
|
7
7
|
|
8
8
|
Rake::TestTask.new(:test) do |t|
|
9
|
-
t.test_files = FileList['test/*_test.rb']
|
9
|
+
t.test_files = FileList['test/*_test.rb'] + FileList['examples/*.rb']
|
10
10
|
end
|
11
11
|
|
12
12
|
# DOCS ########################################################################
|
data/citrus.gemspec
CHANGED
data/doc/syntax.markdown
CHANGED
@@ -104,15 +104,6 @@ levels of precedence is below.
|
|
104
104
|
|
105
105
|
See [Choice](api/classes/Citrus/Choice.html) for more information.
|
106
106
|
|
107
|
-
## Grouping
|
108
|
-
|
109
|
-
As is common in many programming languages, parentheses may be used to override
|
110
|
-
the normal binding order of operators. In the following example parentheses are
|
111
|
-
used to make the vertical bar between `'b'` and `'c'` bind tighter than the
|
112
|
-
space between `'a'` and `'b'`.
|
113
|
-
|
114
|
-
'a' ('b' | 'c') # match "a", then "b" or "c"
|
115
|
-
|
116
107
|
## Labels
|
117
108
|
|
118
109
|
Match objects may be referred to by a different name than the rule that
|
@@ -196,3 +187,12 @@ Operator | Name | Precedence
|
|
196
187
|
`:` | Label | 3
|
197
188
|
`e1 e2` | Sequence | 2
|
198
189
|
<code>e1 | e2</code> | Ordered choice | 1
|
190
|
+
|
191
|
+
## Grouping
|
192
|
+
|
193
|
+
As is common in many programming languages, parentheses may be used to override
|
194
|
+
the normal binding order of operators. In the following example parentheses are
|
195
|
+
used to make the vertical bar between `'b'` and `'c'` bind tighter than the
|
196
|
+
space between `'a'` and `'b'`.
|
197
|
+
|
198
|
+
'a' ('b' | 'c') # match "a", then "b" or "c"
|
data/examples/calc.citrus
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
# A grammar for mathematical formulas that apply basic mathematical operations
|
2
2
|
# to all numbers, respecting operator precedence and grouping of expressions
|
3
|
-
# while ignoring whitespace.
|
4
|
-
#
|
5
|
-
# An identical grammar that is written using pure Ruby can be found in calc.rb.
|
3
|
+
# while ignoring whitespace. This grammar should provide the same interpretation
|
4
|
+
# as Ruby for all mathematical expressions.
|
6
5
|
grammar Calc
|
7
6
|
|
8
7
|
## Hierarchical syntax
|
data/examples/calc.rb
CHANGED
@@ -1,114 +1,121 @@
|
|
1
|
+
# This file contains a suite of tests for the Calc grammar found in calc.citrus.
|
2
|
+
|
1
3
|
require 'citrus'
|
4
|
+
Citrus.require File.expand_path('../calc', __FILE__)
|
5
|
+
require 'test/unit'
|
6
|
+
|
7
|
+
class CalcTest < Test::Unit::TestCase
|
8
|
+
# A helper method that tests the successful parsing and evaluation of the
|
9
|
+
# given mathematical expression.
|
10
|
+
def do_test(expr)
|
11
|
+
match = ::Calc.parse(expr)
|
12
|
+
assert(match)
|
13
|
+
assert_equal(expr, match)
|
14
|
+
assert_equal(expr.length, match.length)
|
15
|
+
assert_equal(eval(expr), match.value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_int
|
19
|
+
do_test('3')
|
20
|
+
end
|
2
21
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
#
|
7
|
-
# An identical grammar that is written using Citrus' own grammar syntax can be
|
8
|
-
# found in calc.citrus.
|
9
|
-
grammar :Calc do
|
22
|
+
def test_float
|
23
|
+
do_test('1.5')
|
24
|
+
end
|
10
25
|
|
11
|
-
|
26
|
+
def test_addition
|
27
|
+
do_test('1+2')
|
28
|
+
end
|
12
29
|
|
13
|
-
|
14
|
-
|
30
|
+
def test_addition_multi
|
31
|
+
do_test('1+2+3')
|
15
32
|
end
|
16
33
|
|
17
|
-
|
18
|
-
|
19
|
-
additive_operator.value(factor.value, term.value)
|
20
|
-
}
|
34
|
+
def test_addition_float
|
35
|
+
do_test('1.5+3')
|
21
36
|
end
|
22
37
|
|
23
|
-
|
24
|
-
|
38
|
+
def test_subtraction
|
39
|
+
do_test('3-2')
|
25
40
|
end
|
26
41
|
|
27
|
-
|
28
|
-
|
29
|
-
multiplicative_operator.value(prefix.value, factor.value)
|
30
|
-
}
|
42
|
+
def test_subtraction_float
|
43
|
+
do_test('4.5-3')
|
31
44
|
end
|
32
45
|
|
33
|
-
|
34
|
-
|
46
|
+
def test_multiplication
|
47
|
+
do_test('2*5')
|
35
48
|
end
|
36
49
|
|
37
|
-
|
38
|
-
|
39
|
-
unary_operator.value(prefix.value)
|
40
|
-
}
|
50
|
+
def test_multiplication_float
|
51
|
+
do_test('1.5*3')
|
41
52
|
end
|
42
53
|
|
43
|
-
|
44
|
-
|
54
|
+
def test_division
|
55
|
+
do_test('20/5')
|
45
56
|
end
|
46
57
|
|
47
|
-
|
48
|
-
|
49
|
-
exponential_operator.value(primary.value, prefix.value)
|
50
|
-
}
|
58
|
+
def test_division_float
|
59
|
+
do_test('4.5/3')
|
51
60
|
end
|
52
61
|
|
53
|
-
|
54
|
-
|
62
|
+
def test_complex
|
63
|
+
do_test('7*4+3.5*(4.5/3)')
|
55
64
|
end
|
56
65
|
|
57
|
-
|
58
|
-
|
59
|
-
term.value
|
60
|
-
}
|
66
|
+
def test_complex_spaced
|
67
|
+
do_test('7 * 4 + 3.5 * (4.5 / 3)')
|
61
68
|
end
|
62
69
|
|
63
|
-
|
70
|
+
def test_complex_with_underscores
|
71
|
+
do_test('(12_000 / 3) * 2.5')
|
72
|
+
end
|
64
73
|
|
65
|
-
|
66
|
-
|
74
|
+
def test_modulo
|
75
|
+
do_test('3 % 2 + 4')
|
67
76
|
end
|
68
77
|
|
69
|
-
|
70
|
-
|
71
|
-
strip.to_f
|
72
|
-
}
|
78
|
+
def test_exponent
|
79
|
+
do_test('2**9')
|
73
80
|
end
|
74
81
|
|
75
|
-
|
76
|
-
|
77
|
-
strip.to_i
|
78
|
-
}
|
82
|
+
def test_exponent_float
|
83
|
+
do_test('2**2.2')
|
79
84
|
end
|
80
85
|
|
81
|
-
|
82
|
-
|
83
|
-
/[0-9]+(?:_[0-9]+)*/
|
86
|
+
def test_negative_exponent
|
87
|
+
do_test('2**-3')
|
84
88
|
end
|
85
89
|
|
86
|
-
|
87
|
-
|
88
|
-
a.send(strip, b)
|
89
|
-
}
|
90
|
+
def test_exponent_exponent
|
91
|
+
do_test('2**2**2')
|
90
92
|
end
|
91
93
|
|
92
|
-
|
93
|
-
|
94
|
-
a.send(strip, b)
|
95
|
-
}
|
94
|
+
def test_exponent_group
|
95
|
+
do_test('2**(3+1)')
|
96
96
|
end
|
97
97
|
|
98
|
-
|
99
|
-
|
100
|
-
a ** b
|
101
|
-
}
|
98
|
+
def test_negative
|
99
|
+
do_test('-5')
|
102
100
|
end
|
103
101
|
|
104
|
-
|
105
|
-
|
106
|
-
# Unary + and - require an @.
|
107
|
-
n.send(strip == '~' ? strip : '%s@' % strip)
|
108
|
-
}
|
102
|
+
def test_double_negative
|
103
|
+
do_test('--5')
|
109
104
|
end
|
110
105
|
|
111
|
-
|
112
|
-
|
113
|
-
|
106
|
+
def test_complement
|
107
|
+
do_test('~4')
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_double_complement
|
111
|
+
do_test('~~4')
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_mixed_unary
|
115
|
+
do_test('~-4')
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_complex_with_negatives
|
119
|
+
do_test('4 * -7 / (8.0 + 1_2)**2')
|
120
|
+
end
|
114
121
|
end
|
@@ -0,0 +1,16 @@
|
|
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
|
+
require 'ipv4address'
|
7
|
+
require 'ipv6address'
|
8
|
+
|
9
|
+
grammar IPAddress
|
10
|
+
include IPv4Address
|
11
|
+
include IPv6Address
|
12
|
+
|
13
|
+
rule IPaddress
|
14
|
+
IPv4address | IPv6address
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
examples = File.expand_path('..', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(examples) unless $LOAD_PATH.include?(examples)
|
3
|
+
|
4
|
+
# This file contains a suite of tests for the IPAddress grammar found in
|
5
|
+
# ipaddress.citrus.
|
6
|
+
|
7
|
+
require 'citrus'
|
8
|
+
Citrus.require 'ipaddress'
|
9
|
+
require 'test/unit'
|
10
|
+
|
11
|
+
class IPAddressTest < Test::Unit::TestCase
|
12
|
+
def test_v4
|
13
|
+
match = IPAddress.parse('1.2.3.4')
|
14
|
+
assert(match)
|
15
|
+
assert_equal(4, match.version)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_v6
|
19
|
+
match = IPAddress.parse('1:2:3:4::')
|
20
|
+
assert(match)
|
21
|
+
assert_equal(6, match.version)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
grammar IPv4Address
|
2
|
+
# A host identified by an IPv4 literal address is represented in
|
3
|
+
# dotted-decimal notation (a sequence of four decimal numbers in the
|
4
|
+
# range 0 to 255, separated by "."), as described in [RFC1123] by
|
5
|
+
# reference to [RFC0952]. Note that other forms of dotted notation may
|
6
|
+
# be interpreted on some platforms, as described in Section 7.4, but
|
7
|
+
# only the dotted-decimal form of four octets is allowed by this
|
8
|
+
# grammar.
|
9
|
+
rule IPv4address
|
10
|
+
(dec-octet '.' dec-octet '.' dec-octet '.' dec-octet) {
|
11
|
+
def version; 4 end
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
rule dec-octet
|
16
|
+
'25' [0-5] # 250-255
|
17
|
+
| '2' [0-4] DIGIT # 200-249
|
18
|
+
| '1' DIGIT DIGIT # 100-199
|
19
|
+
| [1-9] DIGIT # 10-99
|
20
|
+
| DIGIT # 0-9
|
21
|
+
end
|
22
|
+
|
23
|
+
rule DIGIT
|
24
|
+
[0-9]
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
examples = File.expand_path('..', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(examples) unless $LOAD_PATH.include?(examples)
|
3
|
+
|
4
|
+
# This file contains a suite of tests for the IPv4Address grammar found in
|
5
|
+
# ipv4address.citrus.
|
6
|
+
|
7
|
+
require 'citrus'
|
8
|
+
Citrus.require 'ipv4address'
|
9
|
+
require 'test/unit'
|
10
|
+
|
11
|
+
class IPv4AddressTest < Test::Unit::TestCase
|
12
|
+
def test_dec_octet
|
13
|
+
match = IPv4Address.parse('0', :root => :'dec-octet')
|
14
|
+
assert(match)
|
15
|
+
|
16
|
+
match = IPv4Address.parse('255', :root => :'dec-octet')
|
17
|
+
assert(match)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_1
|
21
|
+
match = IPv4Address.parse('0.0.0.0')
|
22
|
+
assert(match)
|
23
|
+
assert_equal(4, match.version)
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_2
|
27
|
+
match = IPv4Address.parse('255.255.255.255')
|
28
|
+
assert(match)
|
29
|
+
assert_equal(4, match.version)
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_invalid
|
33
|
+
assert_raise Citrus::ParseError do
|
34
|
+
IPv4Address.parse('255.255.255.256')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_invalid_short
|
39
|
+
assert_raise Citrus::ParseError do
|
40
|
+
IPv4Address.parse('255.255.255')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_invalid_long
|
45
|
+
assert_raise Citrus::ParseError do
|
46
|
+
IPv4Address.parse('255.255.255.255.255')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,34 +1,4 @@
|
|
1
|
-
|
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
|
1
|
+
require 'ipv4address'
|
32
2
|
|
33
3
|
grammar IPv6Address
|
34
4
|
include IPv4Address
|
@@ -71,12 +41,3 @@ grammar IPv6Address
|
|
71
41
|
DIGIT | [a-fA-F] # Hexadecimal should be case-insensitive.
|
72
42
|
end
|
73
43
|
end
|
74
|
-
|
75
|
-
grammar IPAddress
|
76
|
-
include IPv4Address
|
77
|
-
include IPv6Address
|
78
|
-
|
79
|
-
rule IPaddress
|
80
|
-
IPv4address | IPv6address
|
81
|
-
end
|
82
|
-
end
|