citrus 2.3.2 → 2.3.3
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 +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
|