citrus 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/citrus.gemspec +7 -9
- data/examples/calc.citrus +37 -28
- data/examples/calc.rb +37 -28
- data/lib/citrus.rb +49 -19
- data/lib/citrus/file.rb +7 -9
- data/test/_files/alias.citrus +9 -0
- data/test/_files/grammar1.citrus +1 -0
- data/test/_files/grammar2.citrus +25 -0
- data/test/_files/grammar3.citrus +112 -0
- data/test/_files/rule1.citrus +1 -0
- data/test/_files/rule2.citrus +3 -0
- data/test/_files/rule3.citrus +7 -0
- data/test/_files/super.citrus +13 -0
- data/test/alias_test.rb +3 -3
- data/test/and_predicate_test.rb +1 -1
- data/test/calc_file_test.rb +1 -1
- data/test/calc_test.rb +2 -2
- data/test/choice_test.rb +1 -1
- data/test/expression_test.rb +1 -1
- data/test/file_test.rb +12 -8
- data/test/fixed_width_test.rb +1 -1
- data/test/grammar_test.rb +13 -8
- data/test/helper.rb +23 -57
- data/test/label_test.rb +1 -1
- data/test/match_test.rb +1 -1
- data/test/not_predicate_test.rb +1 -1
- data/test/repeat_test.rb +1 -1
- data/test/rule_test.rb +4 -4
- data/test/sequence_test.rb +1 -1
- data/test/super_test.rb +5 -5
- metadata +22 -9
- data/examples/calc_sugar.rb +0 -87
- data/lib/citrus/sugar.rb +0 -25
- data/test/calc_sugar_test.rb +0 -11
data/citrus.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'citrus'
|
3
|
-
s.version = '1.
|
4
|
-
s.date = '2010-
|
3
|
+
s.version = '1.5.0'
|
4
|
+
s.date = '2010-07-24'
|
5
5
|
|
6
6
|
s.summary = 'Parsing Expressions for Ruby'
|
7
7
|
s.description = 'Parsing Expressions for Ruby'
|
@@ -11,14 +11,12 @@ Gem::Specification.new do |s|
|
|
11
11
|
|
12
12
|
s.require_paths = %w< lib >
|
13
13
|
|
14
|
-
s.files = Dir['benchmark
|
15
|
-
Dir['
|
16
|
-
Dir['
|
17
|
-
Dir['
|
18
|
-
Dir['examples/**/*'] +
|
19
|
-
Dir['extras/**/*'] +
|
14
|
+
s.files = Dir['benchmark/**'] +
|
15
|
+
Dir['doc/**'] +
|
16
|
+
Dir['examples/**'] +
|
17
|
+
Dir['extras/**'] +
|
20
18
|
Dir['lib/**/*.rb'] +
|
21
|
-
Dir['test
|
19
|
+
Dir['test/**/*'] +
|
22
20
|
%w< citrus.gemspec Rakefile README >
|
23
21
|
|
24
22
|
s.test_files = s.files.select {|path| path =~ /^test\/.*_test.rb/ }
|
data/examples/calc.citrus
CHANGED
@@ -7,9 +7,9 @@ grammar Calc
|
|
7
7
|
end
|
8
8
|
|
9
9
|
rule additive
|
10
|
-
(factor operator:
|
10
|
+
(factor operator:(plus | minus) term) {
|
11
11
|
def value
|
12
|
-
operator.apply(factor
|
12
|
+
operator.apply(factor, term)
|
13
13
|
end
|
14
14
|
}
|
15
15
|
end
|
@@ -19,9 +19,9 @@ grammar Calc
|
|
19
19
|
end
|
20
20
|
|
21
21
|
rule multiplicative
|
22
|
-
(primary operator:
|
22
|
+
(primary operator:(star | slash) factor) {
|
23
23
|
def value
|
24
|
-
operator.apply(primary
|
24
|
+
operator.apply(primary, factor)
|
25
25
|
end
|
26
26
|
}
|
27
27
|
end
|
@@ -38,22 +38,6 @@ grammar Calc
|
|
38
38
|
}
|
39
39
|
end
|
40
40
|
|
41
|
-
rule additive_op
|
42
|
-
(plus | minus) {
|
43
|
-
def apply(factor, term)
|
44
|
-
text.strip == '+' ? factor + term : factor - term
|
45
|
-
end
|
46
|
-
}
|
47
|
-
end
|
48
|
-
|
49
|
-
rule multiplicative_op
|
50
|
-
(star | slash) {
|
51
|
-
def apply(primary, factor)
|
52
|
-
text.strip == '*' ? primary * factor : primary / factor
|
53
|
-
end
|
54
|
-
}
|
55
|
-
end
|
56
|
-
|
57
41
|
rule number
|
58
42
|
float | integer
|
59
43
|
end
|
@@ -74,14 +58,39 @@ grammar Calc
|
|
74
58
|
}
|
75
59
|
end
|
76
60
|
|
77
|
-
rule
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
61
|
+
rule plus
|
62
|
+
('+' space) {
|
63
|
+
def apply(factor, term)
|
64
|
+
factor.value + term.value
|
65
|
+
end
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
rule minus
|
70
|
+
('-' space) {
|
71
|
+
def apply(factor, term)
|
72
|
+
factor.value - term.value
|
73
|
+
end
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
rule star
|
78
|
+
('*' space) {
|
79
|
+
def apply(primary, factor)
|
80
|
+
primary.value * factor.value
|
81
|
+
end
|
82
|
+
}
|
83
|
+
end
|
83
84
|
|
84
|
-
rule
|
85
|
-
|
85
|
+
rule slash
|
86
|
+
('/' space) {
|
87
|
+
def apply(primary, factor)
|
88
|
+
primary.value / factor.value
|
89
|
+
end
|
90
|
+
}
|
86
91
|
end
|
92
|
+
|
93
|
+
rule lparen '(' space end
|
94
|
+
rule rparen ')' space end
|
95
|
+
rule space [ \t\n\r]* end
|
87
96
|
end
|
data/examples/calc.rb
CHANGED
@@ -3,17 +3,15 @@ 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
|
-
include Citrus::Grammar
|
8
|
-
|
6
|
+
grammar :Calc do
|
9
7
|
rule :term do
|
10
8
|
any(:additive, :factor)
|
11
9
|
end
|
12
10
|
|
13
11
|
rule :additive do
|
14
|
-
all(:factor, label(:
|
12
|
+
all(:factor, label(any(:plus, :minus), :operator), :term) {
|
15
13
|
def value
|
16
|
-
operator.apply(factor
|
14
|
+
operator.apply(factor, term)
|
17
15
|
end
|
18
16
|
}
|
19
17
|
end
|
@@ -23,9 +21,9 @@ module Calc
|
|
23
21
|
end
|
24
22
|
|
25
23
|
rule :multiplicative do
|
26
|
-
all(:primary, label(:
|
24
|
+
all(:primary, label(any(:star, :slash), :operator), :factor) {
|
27
25
|
def value
|
28
|
-
operator.apply(primary
|
26
|
+
operator.apply(primary, factor)
|
29
27
|
end
|
30
28
|
}
|
31
29
|
end
|
@@ -42,22 +40,6 @@ module Calc
|
|
42
40
|
}
|
43
41
|
end
|
44
42
|
|
45
|
-
rule :additive_op do
|
46
|
-
any(:plus, :minus) {
|
47
|
-
def apply(factor, term)
|
48
|
-
text.strip == '+' ? factor + term : factor - term
|
49
|
-
end
|
50
|
-
}
|
51
|
-
end
|
52
|
-
|
53
|
-
rule :multiplicative_op do
|
54
|
-
any(:star, :slash) {
|
55
|
-
def apply(primary, factor)
|
56
|
-
text.strip == '*' ? primary * factor : primary / factor
|
57
|
-
end
|
58
|
-
}
|
59
|
-
end
|
60
|
-
|
61
43
|
rule :number do
|
62
44
|
any(:float, :integer)
|
63
45
|
end
|
@@ -78,12 +60,39 @@ module Calc
|
|
78
60
|
}
|
79
61
|
end
|
80
62
|
|
63
|
+
rule :plus do
|
64
|
+
all('+', :space) {
|
65
|
+
def apply(factor, term)
|
66
|
+
factor.value + term.value
|
67
|
+
end
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
rule :minus do
|
72
|
+
all('-', :space) {
|
73
|
+
def apply(factor, term)
|
74
|
+
factor.value - term.value
|
75
|
+
end
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
rule :star do
|
80
|
+
all('*', :space) {
|
81
|
+
def apply(primary, factor)
|
82
|
+
primary.value * factor.value
|
83
|
+
end
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
rule :slash do
|
88
|
+
all('/', :space) {
|
89
|
+
def apply(primary, factor)
|
90
|
+
primary.value / factor.value
|
91
|
+
end
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
81
95
|
rule :lparen, ['(', :space]
|
82
96
|
rule :rparen, [')', :space]
|
83
|
-
rule :plus, ['+', :space]
|
84
|
-
rule :minus, ['-', :space]
|
85
|
-
rule :star, ['*', :space]
|
86
|
-
rule :slash, ['/', :space]
|
87
|
-
|
88
97
|
rule :space, /[ \t\n\r]*/
|
89
98
|
end
|
data/lib/citrus.rb
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
#
|
5
5
|
# http://mjijackson.com/citrus
|
6
6
|
module Citrus
|
7
|
-
VERSION = [1,
|
7
|
+
VERSION = [1, 5, 0]
|
8
8
|
|
9
9
|
Infinity = 1.0 / 0
|
10
10
|
|
@@ -86,6 +86,11 @@ module Citrus
|
|
86
86
|
|
87
87
|
# Contains methods that are available to Grammar modules at the class level.
|
88
88
|
module GrammarMethods
|
89
|
+
def self.extend_object(obj)
|
90
|
+
raise ArgumentError, "Grammars must be Ruby modules" unless Module === obj
|
91
|
+
super
|
92
|
+
end
|
93
|
+
|
89
94
|
# Returns the name of this grammar as a string.
|
90
95
|
def name
|
91
96
|
super.to_s
|
@@ -236,7 +241,7 @@ module Citrus
|
|
236
241
|
def ext(rule, mod=nil)
|
237
242
|
rule = Rule.create(rule)
|
238
243
|
mod = Proc.new if block_given?
|
239
|
-
rule.
|
244
|
+
rule.extension = mod if mod
|
240
245
|
rule
|
241
246
|
end
|
242
247
|
|
@@ -387,13 +392,13 @@ module Citrus
|
|
387
392
|
# Specifies a module that will be used to extend all Match objects that
|
388
393
|
# result from this rule. If +mod+ is a Proc, it is used to create an
|
389
394
|
# anonymous module.
|
390
|
-
def
|
395
|
+
def extension=(mod)
|
391
396
|
mod = Module.new(&mod) if Proc === mod
|
392
|
-
@
|
397
|
+
@extension = mod
|
393
398
|
end
|
394
399
|
|
395
400
|
# The module this rule uses to extend new matches.
|
396
|
-
attr_reader :
|
401
|
+
attr_reader :extension
|
397
402
|
|
398
403
|
# Returns +true+ if this rule is a Terminal.
|
399
404
|
def terminal?
|
@@ -419,7 +424,7 @@ module Citrus
|
|
419
424
|
private
|
420
425
|
|
421
426
|
def extend_match(match, name)
|
422
|
-
match.extensions <<
|
427
|
+
match.extensions << extension if extension
|
423
428
|
match.names << name if name
|
424
429
|
match
|
425
430
|
end
|
@@ -931,26 +936,51 @@ module Citrus
|
|
931
936
|
|
932
937
|
alias eql? ==
|
933
938
|
|
934
|
-
# Uses #match to allow sub-matches of this match to be called by name as
|
935
|
-
# instance methods.
|
936
|
-
def method_missing(sym, *args)
|
937
|
-
# Extend this object only when needed and immediately redefine
|
938
|
-
# #method_missing so that the new version is used on all future calls.
|
939
|
-
extensions.each {|e| extend(e) } if @extensions
|
940
|
-
redefine_method_missing!
|
941
|
-
__send__(sym, *args)
|
942
|
-
end
|
943
|
-
|
944
939
|
private
|
945
940
|
|
946
941
|
def redefine_method_missing! # :nodoc:
|
947
942
|
instance_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
948
943
|
def method_missing(sym, *args)
|
949
|
-
|
950
|
-
|
951
|
-
|
944
|
+
if sym == :to_ary
|
945
|
+
original_method_missing(sym, *args)
|
946
|
+
else
|
947
|
+
m = first(sym)
|
948
|
+
return m if m
|
949
|
+
raise 'No match named "%s" in %s (%s)' % [sym, self, name]
|
950
|
+
end
|
952
951
|
end
|
953
952
|
RUBY
|
954
953
|
end
|
954
|
+
|
955
|
+
alias original_method_missing method_missing
|
956
|
+
|
957
|
+
public
|
958
|
+
|
959
|
+
# Allows sub-matches of this match to be retrieved by name as instance
|
960
|
+
# methods.
|
961
|
+
def method_missing(sym, *args)
|
962
|
+
# Extend this object only when needed and immediately redefine
|
963
|
+
# #method_missing so that the new version is used on all future calls.
|
964
|
+
extensions.each {|e| extend(e) } if @extensions
|
965
|
+
redefine_method_missing!
|
966
|
+
__send__(sym, *args)
|
967
|
+
end
|
968
|
+
end
|
969
|
+
end
|
970
|
+
|
971
|
+
class Object
|
972
|
+
# A sugar method for creating grammars.
|
973
|
+
#
|
974
|
+
# grammar :Calc do
|
975
|
+
# end
|
976
|
+
#
|
977
|
+
# module MyModule
|
978
|
+
# grammar :Calc do
|
979
|
+
# end
|
980
|
+
# end
|
981
|
+
#
|
982
|
+
def grammar(name, &block)
|
983
|
+
obj = respond_to?(:const_set) ? self : Object
|
984
|
+
obj.const_set(name, Citrus::Grammar.new(&block))
|
955
985
|
end
|
956
986
|
end
|
data/lib/citrus/file.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
require 'citrus'
|
2
2
|
|
3
3
|
module Citrus
|
4
|
-
# A grammar for Citrus grammar files. This
|
4
|
+
# A grammar for Citrus grammar files. This grammar is used in Citrus#eval to
|
5
5
|
# parse and evaluate Citrus grammars and serves as a prime example of how to
|
6
6
|
# create a complex grammar complete with semantic interpretation in pure Ruby.
|
7
|
-
|
8
|
-
include Grammar
|
7
|
+
File = Grammar.new do
|
9
8
|
|
10
9
|
## Hierarchical syntax
|
11
10
|
|
@@ -119,7 +118,7 @@ module Citrus
|
|
119
118
|
def value
|
120
119
|
rule = suffix.value
|
121
120
|
extension = matches[1].first
|
122
|
-
|
121
|
+
extension.apply(rule) if extension
|
123
122
|
rule
|
124
123
|
end
|
125
124
|
}
|
@@ -217,7 +216,7 @@ module Citrus
|
|
217
216
|
rule :character_class do
|
218
217
|
all(/\[(?:\\?.)*?\]/, :space) {
|
219
218
|
def value
|
220
|
-
Regexp.new(first.text)
|
219
|
+
Regexp.new('\A' + first.text)
|
221
220
|
end
|
222
221
|
}
|
223
222
|
end
|
@@ -225,7 +224,7 @@ module Citrus
|
|
225
224
|
rule :anything_symbol do
|
226
225
|
all('.', :space) {
|
227
226
|
def value
|
228
|
-
/./m #
|
227
|
+
/./m # Match newlines
|
229
228
|
end
|
230
229
|
}
|
231
230
|
end
|
@@ -272,9 +271,8 @@ module Citrus
|
|
272
271
|
|
273
272
|
rule :extension do
|
274
273
|
any(:tag, :block) {
|
275
|
-
def
|
276
|
-
rule.
|
277
|
-
rule
|
274
|
+
def apply(rule)
|
275
|
+
rule.extension = value
|
278
276
|
end
|
279
277
|
}
|
280
278
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
grammar Calc end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
grammar Calc
|
2
|
+
rule number
|
3
|
+
(float | integer) {
|
4
|
+
def value
|
5
|
+
first.value
|
6
|
+
end
|
7
|
+
}
|
8
|
+
end
|
9
|
+
|
10
|
+
rule float
|
11
|
+
(integer '.' integer) {
|
12
|
+
def value
|
13
|
+
text.to_f
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
rule integer
|
19
|
+
[0-9]+ {
|
20
|
+
def value
|
21
|
+
text.to_i
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
grammar Calc
|
2
|
+
# If "additive" were not already the first rule declared in this grammar, we
|
3
|
+
# could use the following line to make it the root rule.
|
4
|
+
#root additive
|
5
|
+
|
6
|
+
rule additive
|
7
|
+
(multitive_additive | multitive) {
|
8
|
+
def value
|
9
|
+
first.value
|
10
|
+
end
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
rule multitive_additive
|
15
|
+
(multitive additive_op additive) {
|
16
|
+
def value
|
17
|
+
if additive_op == '+'
|
18
|
+
multitive.value + additive.value
|
19
|
+
else
|
20
|
+
multitive.value - additive.value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
rule multitive
|
27
|
+
(primary_multitive | primary) {
|
28
|
+
def value
|
29
|
+
first.value
|
30
|
+
end
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
rule primary_multitive
|
35
|
+
(primary multitive_op multitive) {
|
36
|
+
def value
|
37
|
+
if multitive_op == '*'
|
38
|
+
primary.value * multitive.value
|
39
|
+
else
|
40
|
+
primary.value / multitive.value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
}
|
44
|
+
end
|
45
|
+
|
46
|
+
rule primary
|
47
|
+
(additive_paren | number) {
|
48
|
+
def value
|
49
|
+
first.value
|
50
|
+
end
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
rule additive_paren
|
55
|
+
('(' additive ')') {
|
56
|
+
def value
|
57
|
+
additive.value
|
58
|
+
end
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
rule additive_op
|
63
|
+
(plus | minus) {
|
64
|
+
def ==(other)
|
65
|
+
text.strip == other
|
66
|
+
end
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
rule multitive_op
|
71
|
+
(star | slash) {
|
72
|
+
def ==(other)
|
73
|
+
text.strip == other
|
74
|
+
end
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
rule number
|
79
|
+
(float | integer) {
|
80
|
+
def value
|
81
|
+
first.value
|
82
|
+
end
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
rule float
|
87
|
+
(integer '.' integer) {
|
88
|
+
def value
|
89
|
+
text.to_f
|
90
|
+
end
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
rule integer
|
95
|
+
[0-9]+ {
|
96
|
+
def value
|
97
|
+
text.to_i
|
98
|
+
end
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
rule lparen '(' space end
|
103
|
+
rule rparen ')' space end
|
104
|
+
rule plus '+' space end
|
105
|
+
rule minus '-' space end
|
106
|
+
rule star '*' space end
|
107
|
+
rule slash '/' space end
|
108
|
+
|
109
|
+
rule space
|
110
|
+
/[ \t\n\r]*/
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
rule int '' end
|
data/test/alias_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
2
|
Citrus.load(File.dirname(__FILE__) + '/_files/alias')
|
3
3
|
|
4
4
|
class AliasTest < Test::Unit::TestCase
|
@@ -16,8 +16,8 @@ class AliasTest < Test::Unit::TestCase
|
|
16
16
|
|
17
17
|
match = grammar.parse('b')
|
18
18
|
assert(match)
|
19
|
-
|
20
|
-
|
19
|
+
assert_equal('b', match.text)
|
20
|
+
assert_equal(1, match.length)
|
21
21
|
end
|
22
22
|
|
23
23
|
def test_match_renamed
|
data/test/and_predicate_test.rb
CHANGED
data/test/calc_file_test.rb
CHANGED
data/test/calc_test.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
2
|
|
3
3
|
if defined?(Calc)
|
4
4
|
Object.__send__(:remove_const, :Calc)
|
5
5
|
end
|
6
6
|
|
7
|
-
require File.
|
7
|
+
require File.expand_path('../../examples/calc', __FILE__)
|
8
8
|
|
9
9
|
class CalcTest < Test::Unit::TestCase
|
10
10
|
include CalcTestMethods
|
data/test/choice_test.rb
CHANGED
data/test/expression_test.rb
CHANGED
data/test/file_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
2
|
require 'citrus/file'
|
3
3
|
|
4
4
|
class CitrusFileTest < Test::Unit::TestCase
|
@@ -233,17 +233,17 @@ class CitrusFileTest < Test::Unit::TestCase
|
|
233
233
|
match = grammar.parse('"" <Module>')
|
234
234
|
assert(match)
|
235
235
|
assert_kind_of(Rule, match.value)
|
236
|
-
assert_kind_of(Module, match.value.
|
236
|
+
assert_kind_of(Module, match.value.extension)
|
237
237
|
|
238
238
|
match = grammar.parse('"" {}')
|
239
239
|
assert(match)
|
240
240
|
assert_kind_of(Rule, match.value)
|
241
|
-
assert_kind_of(Module, match.value.
|
241
|
+
assert_kind_of(Module, match.value.extension)
|
242
242
|
|
243
243
|
match = grammar.parse('"" {} ')
|
244
244
|
assert(match)
|
245
245
|
assert_kind_of(Rule, match.value)
|
246
|
-
assert_kind_of(Module, match.value.
|
246
|
+
assert_kind_of(Module, match.value.extension)
|
247
247
|
end
|
248
248
|
|
249
249
|
def test_suffix
|
@@ -408,19 +408,23 @@ class CitrusFileTest < Test::Unit::TestCase
|
|
408
408
|
|
409
409
|
match = grammar.parse('[_]')
|
410
410
|
assert(match)
|
411
|
-
assert_equal(
|
411
|
+
assert_equal(/\A[_]/, match.value)
|
412
412
|
|
413
413
|
match = grammar.parse('[a-z]')
|
414
414
|
assert(match)
|
415
|
-
assert_equal(
|
415
|
+
assert_equal(/\A[a-z]/, match.value)
|
416
416
|
|
417
417
|
match = grammar.parse('[a-z0-9]')
|
418
418
|
assert(match)
|
419
|
-
assert_equal(
|
419
|
+
assert_equal(/\A[a-z0-9]/, match.value)
|
420
|
+
|
421
|
+
match = grammar.parse('[\[-\]]')
|
422
|
+
assert(match)
|
423
|
+
assert_equal(/\A[\[-\]]/, match.value)
|
420
424
|
|
421
425
|
match = grammar.parse('[\\x26-\\x29]')
|
422
426
|
assert(match)
|
423
|
-
assert_equal(
|
427
|
+
assert_equal(/\A[\x26-\x29]/, match.value)
|
424
428
|
end
|
425
429
|
|
426
430
|
def test_anything_symbol
|
data/test/fixed_width_test.rb
CHANGED
data/test/grammar_test.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
2
|
|
3
3
|
class GrammarTest < Test::Unit::TestCase
|
4
4
|
|
5
5
|
def test_new
|
6
|
-
Grammar.new
|
7
|
-
|
8
|
-
|
6
|
+
g = Grammar.new
|
7
|
+
assert_kind_of(Module, g)
|
8
|
+
assert(g.include?(Grammar))
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_non_module_fail
|
12
|
+
assert_raise ArgumentError do
|
13
|
+
''.extend(GrammarMethods)
|
9
14
|
end
|
10
15
|
end
|
11
16
|
|
@@ -116,14 +121,14 @@ class GrammarTest < Test::Unit::TestCase
|
|
116
121
|
|
117
122
|
match = grammar.parse('((a))')
|
118
123
|
assert(match)
|
119
|
-
|
120
|
-
|
124
|
+
assert_equal('((a))', match.text)
|
125
|
+
assert_equal(5, match.length)
|
121
126
|
|
122
127
|
str = ('(' * 200) + 'a' + (')' * 200)
|
123
128
|
match = grammar.parse(str)
|
124
129
|
assert(match)
|
125
|
-
|
126
|
-
|
130
|
+
assert_equal(str, match.text)
|
131
|
+
assert_equal(str.length, match.length)
|
127
132
|
end
|
128
133
|
|
129
134
|
end
|
data/test/helper.rb
CHANGED
@@ -44,100 +44,66 @@ class Test::Unit::TestCase
|
|
44
44
|
end
|
45
45
|
|
46
46
|
module CalcTestMethods
|
47
|
-
|
48
|
-
|
47
|
+
# A helper method that tests the successful parsing and evaluation of the
|
48
|
+
# given mathematical expression.
|
49
|
+
def do_test(expr)
|
50
|
+
match = Calc.parse(expr)
|
49
51
|
assert(match)
|
50
|
-
assert_equal(
|
51
|
-
assert_equal(
|
52
|
-
assert_equal(
|
52
|
+
assert_equal(expr, match.text)
|
53
|
+
assert_equal(expr.length, match.length)
|
54
|
+
assert_equal(eval(expr), match.value)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_int
|
58
|
+
do_test('3')
|
53
59
|
end
|
54
60
|
|
55
61
|
def test_float
|
56
|
-
|
57
|
-
assert(match)
|
58
|
-
assert_equal('1.5', match.text)
|
59
|
-
assert_equal(3, match.length)
|
60
|
-
assert_equal(1.5, match.value)
|
62
|
+
do_test('1.5')
|
61
63
|
end
|
62
64
|
|
63
65
|
def test_addition
|
64
|
-
|
65
|
-
assert(match)
|
66
|
-
assert_equal('1+2', match.text)
|
67
|
-
assert_equal(3, match.length)
|
68
|
-
assert_equal(3, match.value)
|
66
|
+
do_test('1+2')
|
69
67
|
end
|
70
68
|
|
71
69
|
def test_addition_multi
|
72
|
-
|
73
|
-
assert(match)
|
74
|
-
assert_equal('1+2+3', match.text)
|
75
|
-
assert_equal(5, match.length)
|
76
|
-
assert_equal(6, match.value)
|
70
|
+
do_test('1+2+3')
|
77
71
|
end
|
78
72
|
|
79
73
|
def test_addition_float
|
80
|
-
|
81
|
-
assert(match)
|
82
|
-
assert_equal('1.5+3', match.text)
|
83
|
-
assert_equal(5, match.length)
|
84
|
-
assert_equal(4.5, match.value)
|
74
|
+
do_test('1.5+3')
|
85
75
|
end
|
86
76
|
|
87
77
|
def test_subtraction
|
88
|
-
|
89
|
-
assert(match)
|
90
|
-
assert_equal(1, match.value)
|
78
|
+
do_test('3-2')
|
91
79
|
end
|
92
80
|
|
93
81
|
def test_subtraction_float
|
94
|
-
|
95
|
-
assert(match)
|
96
|
-
assert_equal('4.5-3', match.text)
|
97
|
-
assert_equal(5, match.length)
|
98
|
-
assert_equal(1.5, match.value)
|
82
|
+
do_test('4.5-3')
|
99
83
|
end
|
100
84
|
|
101
85
|
def test_multiplication
|
102
|
-
|
103
|
-
assert(match)
|
104
|
-
assert_equal(10, match.value)
|
86
|
+
do_test('2*5')
|
105
87
|
end
|
106
88
|
|
107
89
|
def test_multiplication_float
|
108
|
-
|
109
|
-
assert(match)
|
110
|
-
assert_equal('1.5*3', match.text)
|
111
|
-
assert_equal(5, match.length)
|
112
|
-
assert_equal(4.5, match.value)
|
90
|
+
do_test('1.5*3')
|
113
91
|
end
|
114
92
|
|
115
93
|
def test_division
|
116
|
-
|
117
|
-
assert(match)
|
118
|
-
assert_equal(4, match.value)
|
94
|
+
do_test('20/5')
|
119
95
|
end
|
120
96
|
|
121
97
|
def test_division_float
|
122
|
-
|
123
|
-
assert(match)
|
124
|
-
assert_equal('4.5/3', match.text)
|
125
|
-
assert_equal(5, match.length)
|
126
|
-
assert_equal(1.5, match.value)
|
98
|
+
do_test('4.5/3')
|
127
99
|
end
|
128
100
|
|
129
101
|
def test_complex
|
130
|
-
|
131
|
-
assert(match)
|
132
|
-
assert_equal('7*4+3.5*(4.5/3)', match.text)
|
133
|
-
assert_equal(33.25, match.value)
|
102
|
+
do_test('7*4+3.5*(4.5/3)')
|
134
103
|
end
|
135
104
|
|
136
105
|
def test_complex_spaced
|
137
|
-
|
138
|
-
assert(match)
|
139
|
-
assert_equal(33.25, match.value)
|
106
|
+
do_test('7 * 4 + 3.5 * (4.5 / 3)')
|
140
107
|
end
|
141
108
|
end
|
142
|
-
|
143
109
|
end
|
data/test/label_test.rb
CHANGED
data/test/match_test.rb
CHANGED
data/test/not_predicate_test.rb
CHANGED
data/test/repeat_test.rb
CHANGED
data/test/rule_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
2
|
|
3
3
|
class RuleTest < Test::Unit::TestCase
|
4
4
|
|
@@ -22,7 +22,7 @@ class RuleTest < Test::Unit::TestCase
|
|
22
22
|
|
23
23
|
def test_match_module
|
24
24
|
rule = EqualRule.new('a')
|
25
|
-
rule.
|
25
|
+
rule.extension = MatchModule
|
26
26
|
match = rule.match(input('a'))
|
27
27
|
assert(match)
|
28
28
|
assert_equal(:test, match.a_test)
|
@@ -30,7 +30,7 @@ class RuleTest < Test::Unit::TestCase
|
|
30
30
|
|
31
31
|
def test_numeric_proc
|
32
32
|
rule = EqualRule.new(1)
|
33
|
-
rule.
|
33
|
+
rule.extension = NumericProc
|
34
34
|
match = rule.match(input('1'))
|
35
35
|
assert(match)
|
36
36
|
assert_equal(1, match.to_i)
|
@@ -39,7 +39,7 @@ class RuleTest < Test::Unit::TestCase
|
|
39
39
|
|
40
40
|
def test_numeric_module
|
41
41
|
rule = EqualRule.new(1)
|
42
|
-
rule.
|
42
|
+
rule.extension = NumericModule
|
43
43
|
match = rule.match(input('1'))
|
44
44
|
assert(match)
|
45
45
|
assert_equal(1, match.to_i)
|
data/test/sequence_test.rb
CHANGED
data/test/super_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
2
|
Citrus.load(File.dirname(__FILE__) + '/_files/super')
|
3
3
|
|
4
4
|
class SuperTest < Test::Unit::TestCase
|
@@ -20,13 +20,13 @@ class SuperTest < Test::Unit::TestCase
|
|
20
20
|
|
21
21
|
match = grammar2.parse('b')
|
22
22
|
assert(match)
|
23
|
-
|
24
|
-
|
23
|
+
assert_equal('b', match.text)
|
24
|
+
assert_equal(1, match.length)
|
25
25
|
|
26
26
|
match = grammar2.parse('a')
|
27
27
|
assert(match)
|
28
|
-
|
29
|
-
|
28
|
+
assert_equal('a', match.text)
|
29
|
+
assert_equal(1, match.length)
|
30
30
|
end
|
31
31
|
|
32
32
|
def test_peg
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: citrus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 3
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 1
|
7
|
-
-
|
8
|
+
- 5
|
8
9
|
- 0
|
9
|
-
version: 1.
|
10
|
+
version: 1.5.0
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Michael Jackson
|
@@ -14,16 +15,18 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2010-
|
18
|
+
date: 2010-07-24 00:00:00 -06:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
21
22
|
name: builder
|
22
23
|
prerelease: false
|
23
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
24
26
|
requirements:
|
25
27
|
- - ">="
|
26
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
27
30
|
segments:
|
28
31
|
- 0
|
29
32
|
version: "0"
|
@@ -33,9 +36,11 @@ dependencies:
|
|
33
36
|
name: rake
|
34
37
|
prerelease: false
|
35
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
36
40
|
requirements:
|
37
41
|
- - ">="
|
38
42
|
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
39
44
|
segments:
|
40
45
|
- 0
|
41
46
|
version: "0"
|
@@ -50,9 +55,9 @@ extensions: []
|
|
50
55
|
extra_rdoc_files:
|
51
56
|
- README
|
52
57
|
files:
|
53
|
-
- benchmark/seqpar.rb
|
54
58
|
- benchmark/seqpar.citrus
|
55
59
|
- benchmark/seqpar.gnuplot
|
60
|
+
- benchmark/seqpar.rb
|
56
61
|
- doc/background.rdoc
|
57
62
|
- doc/example.rdoc
|
58
63
|
- doc/index.rdoc
|
@@ -61,16 +66,21 @@ files:
|
|
61
66
|
- doc/syntax.rdoc
|
62
67
|
- examples/calc.citrus
|
63
68
|
- examples/calc.rb
|
64
|
-
- examples/calc_sugar.rb
|
65
69
|
- extras/citrus.vim
|
66
70
|
- lib/citrus/debug.rb
|
67
71
|
- lib/citrus/file.rb
|
68
|
-
- lib/citrus/sugar.rb
|
69
72
|
- lib/citrus.rb
|
73
|
+
- test/_files/alias.citrus
|
74
|
+
- test/_files/grammar1.citrus
|
75
|
+
- test/_files/grammar2.citrus
|
76
|
+
- test/_files/grammar3.citrus
|
77
|
+
- test/_files/rule1.citrus
|
78
|
+
- test/_files/rule2.citrus
|
79
|
+
- test/_files/rule3.citrus
|
80
|
+
- test/_files/super.citrus
|
70
81
|
- test/alias_test.rb
|
71
82
|
- test/and_predicate_test.rb
|
72
83
|
- test/calc_file_test.rb
|
73
|
-
- test/calc_sugar_test.rb
|
74
84
|
- test/calc_test.rb
|
75
85
|
- test/choice_test.rb
|
76
86
|
- test/expression_test.rb
|
@@ -103,23 +113,27 @@ rdoc_options:
|
|
103
113
|
require_paths:
|
104
114
|
- lib
|
105
115
|
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
none: false
|
106
117
|
requirements:
|
107
118
|
- - ">="
|
108
119
|
- !ruby/object:Gem::Version
|
120
|
+
hash: 3
|
109
121
|
segments:
|
110
122
|
- 0
|
111
123
|
version: "0"
|
112
124
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
113
126
|
requirements:
|
114
127
|
- - ">="
|
115
128
|
- !ruby/object:Gem::Version
|
129
|
+
hash: 3
|
116
130
|
segments:
|
117
131
|
- 0
|
118
132
|
version: "0"
|
119
133
|
requirements: []
|
120
134
|
|
121
135
|
rubyforge_project:
|
122
|
-
rubygems_version: 1.3.
|
136
|
+
rubygems_version: 1.3.7
|
123
137
|
signing_key:
|
124
138
|
specification_version: 3
|
125
139
|
summary: Parsing Expressions for Ruby
|
@@ -127,7 +141,6 @@ test_files:
|
|
127
141
|
- test/alias_test.rb
|
128
142
|
- test/and_predicate_test.rb
|
129
143
|
- test/calc_file_test.rb
|
130
|
-
- test/calc_sugar_test.rb
|
131
144
|
- test/calc_test.rb
|
132
145
|
- test/choice_test.rb
|
133
146
|
- test/expression_test.rb
|
data/examples/calc_sugar.rb
DELETED
@@ -1,87 +0,0 @@
|
|
1
|
-
require 'citrus/sugar'
|
2
|
-
|
3
|
-
# A grammar for mathematical formulas that apply the basic four operations to
|
4
|
-
# non-negative numbers (integers and floats), respecting operator precedence and
|
5
|
-
# ignoring whitespace.
|
6
|
-
Calc = Citrus::Grammar.new {
|
7
|
-
rule term do
|
8
|
-
any(additive, factor)
|
9
|
-
end
|
10
|
-
|
11
|
-
rule additive do
|
12
|
-
all(factor, label(additive_op, operator), term) {
|
13
|
-
def value
|
14
|
-
operator.apply(factor.value, term.value)
|
15
|
-
end
|
16
|
-
}
|
17
|
-
end
|
18
|
-
|
19
|
-
rule factor do
|
20
|
-
any(multiplicative, primary)
|
21
|
-
end
|
22
|
-
|
23
|
-
rule multiplicative do
|
24
|
-
all(primary, label(multiplicative_op, operator), factor) {
|
25
|
-
def value
|
26
|
-
operator.apply(primary.value, factor.value)
|
27
|
-
end
|
28
|
-
}
|
29
|
-
end
|
30
|
-
|
31
|
-
rule primary do
|
32
|
-
any(term_paren, number)
|
33
|
-
end
|
34
|
-
|
35
|
-
rule term_paren do
|
36
|
-
all(lparen, term, rparen) {
|
37
|
-
def value
|
38
|
-
term.value
|
39
|
-
end
|
40
|
-
}
|
41
|
-
end
|
42
|
-
|
43
|
-
rule additive_op do
|
44
|
-
any(plus, minus) {
|
45
|
-
def apply(factor, term)
|
46
|
-
text.strip == '+' ? factor + term : factor - term
|
47
|
-
end
|
48
|
-
}
|
49
|
-
end
|
50
|
-
|
51
|
-
rule multiplicative_op do
|
52
|
-
any(star, slash) {
|
53
|
-
def apply(primary, factor)
|
54
|
-
text.strip == '*' ? primary * factor : primary / factor
|
55
|
-
end
|
56
|
-
}
|
57
|
-
end
|
58
|
-
|
59
|
-
rule number do
|
60
|
-
any(float, integer)
|
61
|
-
end
|
62
|
-
|
63
|
-
rule float do
|
64
|
-
all(/[0-9]+/, '.', /[0-9]+/, space) {
|
65
|
-
def value
|
66
|
-
text.strip.to_f
|
67
|
-
end
|
68
|
-
}
|
69
|
-
end
|
70
|
-
|
71
|
-
rule integer do
|
72
|
-
all(/[0-9]+/, space) {
|
73
|
-
def value
|
74
|
-
text.strip.to_i
|
75
|
-
end
|
76
|
-
}
|
77
|
-
end
|
78
|
-
|
79
|
-
rule lparen, ['(', space]
|
80
|
-
rule rparen, [')', space]
|
81
|
-
rule plus, ['+', space]
|
82
|
-
rule minus, ['-', space]
|
83
|
-
rule star, ['*', space]
|
84
|
-
rule slash, ['/', space]
|
85
|
-
|
86
|
-
rule space, /[ \t\n\r]*/
|
87
|
-
}
|
data/lib/citrus/sugar.rb
DELETED
@@ -1,25 +0,0 @@
|
|
1
|
-
require 'citrus'
|
2
|
-
|
3
|
-
module Citrus
|
4
|
-
module GrammarMethods
|
5
|
-
# Permits creation of aliases within rule definitions in Ruby grammars using
|
6
|
-
# the bare name of another rule instead of a Symbol, e.g.:
|
7
|
-
#
|
8
|
-
# rule :value do
|
9
|
-
# any(:alpha, :num)
|
10
|
-
# end
|
11
|
-
#
|
12
|
-
# can now be written as
|
13
|
-
#
|
14
|
-
# rule value do
|
15
|
-
# any(alpha, num)
|
16
|
-
# end
|
17
|
-
#
|
18
|
-
# The only caveat is that since this hack uses +method_missing+ you must
|
19
|
-
# still use symbols for rules that have the same name as any of the methods
|
20
|
-
# in GrammarMethods (root, rule, rules, etc.)
|
21
|
-
def method_missing(sym, *args)
|
22
|
-
sym
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
data/test/calc_sugar_test.rb
DELETED